#line 16 "ag-http.c-nw" static char copyright[] = "Copyright NBBI, Den Haag, 1995"; /* Author: Bert Bos */ #define USE_POLL 0 #include #if USE_POLL #include #endif #include #include #include #include #include /* To find who we are */ #include #include /* connectTCP() */ #include /* String and heap functions */ #include /* URL parsing */ #include /* Read/parse MIME headers */ #ifndef howmany #define howmany(x,y) (((x)+((y)-1))/(y)) #endif #define NRBITS (sizeof(int) * 8) /* 8 bits per byte */ static struct { Bool status_has_been_read; /* FALSE until header read */ Bool header_has_been_read; /* FALSE until header read */ Bool non_blocking_io; /* O_NONBLOCK has been set? */ W3ADocumentInfo info; FILE *f; char *headerbuf; /* Temporarily store MIME */ int headerlen; /* Length of headerbuf */ #ifndef OLD char *query; /* Query to send to server */ int querylen, queryoffset; /* Length/offset in query */ #endif } *conn_info[FD_SETSIZE]; #line 69 "ag-http.c-nw" #define HTTPVERSION "HTTP/1.0" /* HTTP protocol version */ #define MAXHOSTNAMELEN 256 /* Name of local machine */ static Strip proxy; /* = "proxy" */ #ifdef OLD static void send_HTRQ_headers(FILE *f, const char *referer) { W3ABrowserInfo info; /* Accepted formats */ int i; #if 0 { struct passwd *pwent; /* Info about user */ struct hostent *phe; /* Info about localhost */ char host[MAXHOSTNAMELEN]; /* Name of local machine */ if ((pwent = getpwuid(getuid())) && (gethostname(host, sizeof(host)) == 0) && (phe = gethostbyname(host))) fprintf(f, "From: %s@%s\r\n", pwent->pw_name, phe->h_name); } #endif /* 0 */ W3AbrowserInfo(&info); for (i = 0; i < info.nformats; i++) if (info.preferences[i] == 1.0) fprintf(f, "Accept: %s\015\012", info.formats[i]); else fprintf(f, "Accept: %s; q=%f\015\012", info.formats[i], info.preferences[i]); fprintf(f, "User-Agent: %s\015\012", info.version); if (referer) fprintf(f, "Referer: %s\015\012", referer); } static Bool send_HTTP_request(FILE *f, URI uri, int method, const char *referer) { char *path, *search, *meth; path = strip2str(uri.path); search = uri.search ? strip2str(uri.search) : NULL; switch (method) { case GET_METHOD: meth = "GET"; break; case PUT_METHOD: meth = "PUT"; break; case POST_METHOD: meth = "POST"; break; case HEAD_METHOD: meth = "HEAD"; break; default: errno = EMETHOD; return FALSE; /* Illegal method */ } fprintf(f, "%s %s%s%s %s\r\n", meth, strip2str(uri.path), search ? "?" : "", search ? search : "", HTTPVERSION); send_HTRQ_headers(f, referer); if (method != PUT_METHOD && method != POST_METHOD) fprintf(f, "\r\n"); /* End of headers */ fflush(f); /* Make ready for read */ return TRUE; } #endif /* OLD */ #line 142 "ag-http.c-nw" #ifndef OLD static Bool ready_for_write(int fd) { #if USE_POLL struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLOUT; /* return: -1 = err; 0 = timed out; 1 = input available */ return poll(fds, 1, 0) > 0; #else /* USE_POLL */ int mask[howmany(FD_SETSIZE, NRBITS)]; struct timeval timeout; int n, i; timeout.tv_sec = 0; timeout.tv_usec = 0; for (i = 0; i < XtNumber(mask); i++) mask[i] = 0; mask[fd/NRBITS] |= 1 << (fd % NRBITS); return select(fd + 1, NULL, mask, NULL, &timeout) > 0; #endif /* USE_POLL */ } static Bool send_request(int fd) { int n; if (conn_info[fd]->queryoffset != conn_info[fd]->querylen && (!conn_info[fd]->non_blocking_io || ready_for_write(fd))) { n = write(fd, conn_info[fd]->query + conn_info[fd]->queryoffset, conn_info[fd]->querylen - conn_info[fd]->queryoffset); if (n == -1) return FALSE; conn_info[fd]->queryoffset += n; } if (conn_info[fd]->queryoffset != conn_info[fd]->querylen) { errno = EAGAIN; return FALSE; } else { return TRUE; } } #endif /* OLD */ #line 192 "ag-http.c-nw" #ifndef OLD static Bool create_HTTP_request(int fd, Bool is_proxy, URI uri, int method, const char *referer) { char *path, *search, *meth, *s; W3ABrowserInfo info; /* Accepted formats */ int i, n, len; path = strip2str(uri.path); if (is_proxy) { assert(path[0] == '/'); path++; } search = uri.search ? strip2str(uri.search) : NULL; switch (method) { case GET_METHOD: meth = "GET"; break; case PUT_METHOD: meth = "PUT"; break; case POST_METHOD: meth = "POST"; break; case HEAD_METHOD: meth = "HEAD"; break; default: errno = EMETHOD; return FALSE; /* Illegal method */ } W3AbrowserInfo(&info); n = strlen(meth) + strlen(path) + (search ? strlen(search) : 0) + strlen(HTTPVERSION) + strlen(info.version) + strlen(referer) + 50; for (i = 0; i < info.nformats; i++) n += strlen(info.formats[i]) + (info.preferences[i] == 1.0 ? 10 : 35); newarray(s, n); len = sprintf(s, "%s %s%s%s %s\r\n", meth, path, search ? "?" : "", search ? search : "", HTTPVERSION); for (i = 0; i < info.nformats; i++) { if (info.preferences[i] == 1.0) len += sprintf(s + len, "Accept: %s\015\012", info.formats[i]); else len += sprintf(s + len, "Accept: %s; q=%f\015\012", info.formats[i], info.preferences[i]); } len += sprintf(s + len, "User-Agent: %s\015\012", info.version); if (referer) len += sprintf(s + len, "Referer: %s\015\012", referer); if (method != PUT_METHOD && method != POST_METHOD) len += sprintf(s + len, "\r\n"); /* End of headers */ assert(len < n); /* Allocated enough space? */ conn_info[fd]->query = s; conn_info[fd]->queryoffset = 0; conn_info[fd]->querylen = len; return TRUE; } #endif /* OLD */ #line 252 "ag-http.c-nw" EXPORT Bool initHTTP(char ***protocols, int *nrprotocols) { static char *protos[] = {"http", "proxy"}; *protocols = protos; *nrprotocols = XtNumber(protos); proxy = str2strip("proxy"); return TRUE; } EXPORT int openHTTP(const char *url, int method, int flags, const char *referer) { URI uri; char *host, *port; int s; FILE *f; Bool is_proxy; if (! URL_parse(url, &uri)) { errno = EURL; /* Bad URL syntax */ return -1; } is_proxy = uri.scheme == proxy; port = uri.port ? strip2str(uri.port) : "80"; host = strip2str(uri.host); /* if ((s = connectTCP(host, port, flags & O_NONBLOCK)) == -1 */ if ((s = connectTCP(host, port, FALSE)) == -1 && errno != EINPROGRESS) return -1; /* Could not connect */ if (!(f = fdopen(s, "r+"))) return -1; /* I/O error */ #ifdef OLD if (!send_HTTP_request(f, uri, method, referer)) return -1; /* Illegal method */ #endif #if 1 if ((flags & O_NONBLOCK) != 0 && fcntl(s, F_SETFL, O_NONBLOCK) == -1) return -1; /* I/O error */ #endif new(conn_info[s]); conn_info[s]->info.url = newstring(url); conn_info[s]->info.mime_type = NULL; conn_info[s]->info.mime_params = NULL; conn_info[s]->info.title = NULL; conn_info[s]->info.referer = newstring(referer); conn_info[s]->info.status = NULL; conn_info[s]->info.location = NULL; conn_info[s]->header_has_been_read = FALSE; conn_info[s]->status_has_been_read = FALSE; conn_info[s]->non_blocking_io = (flags & O_NONBLOCK) != 0; conn_info[s]->f = f; conn_info[s]->headerbuf = NULL; conn_info[s]->headerlen = 0; #ifndef OLD if (! create_HTTP_request(s, is_proxy, uri, method, referer)) return -1; if ((flags & O_NONBLOCK) == 0 && ! send_request(s)) return -1; #endif return s; } EXPORT Bool doneHTTP(int fd) { assert(0 <= fd && fd < FD_SETSIZE && conn_info[fd]); return send_request(fd); } EXPORT int peekHTTP(int fd) { #if USE_POLL struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLNORM; /* return: -1 = err; 0 = timed out; 1 = input available */ return poll(fds, 1, 0); #else /* USE_POLL */ int mask[howmany(FD_SETSIZE, NRBITS)]; struct timeval timeout; int n, i; timeout.tv_sec = 0; timeout.tv_usec = 0; for (i = 0; i < XtNumber(mask); i++) mask[i] = 0; mask[fd/NRBITS] |= 1 << (fd % NRBITS); if ((n = select(fd + 1, mask, NULL, NULL, &timeout)) == -1) return -1; return n; /* 0 or 1 */ #endif /* USE_POLL */ } /* read_status -- read the server's response code */ static Bool read_status(int s) { char buf[BUFSIZ]; FILE *f = conn_info[s]->f; Bool ok; int i; ok = fgets(buf, sizeof(buf), f) != NULL; /* Read status line */ if (! ok) /* No data available */ return errno == EAGAIN; /* I/O error?*/ if (! n_eq(buf, "HTTP", 4)) { /* Old server: HTTP 0.9 */ conn_info[s]->info.mime_type = newstring("text/html"); /* Sorry, we loose the first line... */ } else { /* HTTP/1.0 or newer */ for (i = 4; !isspace(buf[i]); i++) ; for (; buf[i] && isspace(buf[i]); i++) ; conn_info[s]->info.status = newstring(buf + i); } conn_info[s]->status_has_been_read = TRUE; return TRUE; } /* interpose_decoder -- start gzip -d -c as a subprocess */ static Bool interpose_decoder(int s) { int fd[2]; /* Pipe */ long c, tblsiz; fflush(conn_info[s]->f); if (pipe(fd) == -1) return FALSE; /* Create pipe */ switch (fork()) { /* Fork process */ case -1: return FALSE; /* Out of processes */ case 0: /* A subprocess that reads data from the original socket */ if (conn_info[s]->non_blocking_io) /* Remove non-blocking IO */ if (fcntl(s, F_SETFL, 0) == -1) exit(1); if (close(0) == -1 || dup(s) != 0) exit(1); if (close(1) == -1 || dup(fd[1]) != 1) exit(1); /* if (close(2) == -1 || dup(fd[1]) != 2) exit(1); */ /* Close all unneeded files */ tblsiz = sysconf(_SC_OPEN_MAX); for (c = 3; c < tblsiz; c++) (void) close(c); debug("Started gzip -d -c\n"); execlp("gzip", "gzip", "-d", "-c", NULL); /* NOTREACHED */ } if (close(fd[1]) == -1) return FALSE; /* I/O Error */ /* Rename the pipe to s, so that the subprocess becomes `invisible' */ if (close(s) == -1 || dup2(fd[0], s) == -1) return FALSE; if (conn_info[s]->non_blocking_io) /* Restore non-blocking IO */ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) return FALSE; return TRUE; } /* read_MIME_header -- read the MIME header lines and parse them */ static Bool read_MIME_header(int s) { MIME_header header; FILE *f = conn_info[s]->f; char line[BUFSIZ]; int h; #if 0 /* TO DO: Something needs to be done to make this non-blocking... */ read_header(f, &header, NULL); #else do { if (! fgets(line, sizeof(line), f)) return FALSE; /* Might be EAGAIN */ h = conn_info[s]->headerlen + strlen(line); renewarray(conn_info[s]->headerbuf, h + 1); strcpy(conn_info[s]->headerbuf + conn_info[s]->headerlen, line); conn_info[s]->headerlen = h; } while (line[0] != '\015' && line[0] != '\012'); /* End of header */ parse_header(conn_info[s]->headerbuf, &header); dispose(conn_info[s]->headerbuf); #endif if (header.head[Location]) conn_info[s]->info.location = newstring(header.head[Location]); if (header.head[Title]) conn_info[s]->info.title = newstring(header.head[Title]); if (header.head[Base]) conn_info[s]->info.url = newstring(header.head[Base]); if (header.head[Content_Length]) conn_info[s]->info.size = atol(header.head[Content_Length]); if (header.head[Content_Type]) conn_info[s]->info.mime_type = newstring(header.head[Content_Type]); else conn_info[s]->info.mime_type = newstring("text/html"); conn_info[s]->header_has_been_read = TRUE; /* Something with mime_params, charset?... */ /* Check for encoding */ switch (header.content_transfer_encoding) { case MIME_8bit: case MIME_7bit: break; /* No encoding */ case MIME_quoted_printable: case MIME_base64: errno = EFORMAT; /* Not yet implemented */ return FALSE; case MIME_x_gzip: return interpose_decoder(s); /* Call gzip -d -c */ default: errno = EFORMAT; return FALSE; } return TRUE; } EXPORT int readHTTP(int fd, char *buf, size_t nbytes) { assert(conn_info[fd]); if (conn_info[fd]->non_blocking_io) { if (!conn_info[fd]->status_has_been_read) { if (read_status(fd)) errno = EAGAIN; return -1; } if (!conn_info[fd]->header_has_been_read) { if (read_MIME_header(fd)) errno = EAGAIN; return -1; } } else { if (!conn_info[fd]->status_has_been_read) if (!read_status(fd)) return -1; if (!conn_info[fd]->header_has_been_read) if (!read_MIME_header(fd)) return -1; } return fread(buf, 1, nbytes, conn_info[fd]->f); } EXPORT int writeHTTP(int fd, const char *buf, size_t nbytes) { assert(conn_info[fd]); return write(fd, buf, nbytes); } EXPORT Bool infoHTTP(int fd, W3ADocumentInfo *buf) { assert(conn_info[fd]); if (!conn_info[fd]->status_has_been_read && !read_status(fd)) return FALSE; if (!conn_info[fd]->header_has_been_read && !read_MIME_header(fd)) return FALSE; buf->mime_type = newstring(conn_info[fd]->info.mime_type); buf->mime_params = newstring(conn_info[fd]->info.mime_params); buf->title = newstring(conn_info[fd]->info.title); buf->status = newstring(conn_info[fd]->info.status); buf->location = newstring(conn_info[fd]->info.location); buf->size = conn_info[fd]->info.size; return TRUE; } EXPORT Bool closeHTTP(int fd) { int status; assert(conn_info[fd]); status = fclose(conn_info[fd]->f); dispose(conn_info[fd]->headerbuf); #ifndef OLD dispose(conn_info[fd]->query); #endif dispose(conn_info[fd]); return status != -1; } EXPORT Bool deleteHTTP(const char *url) { errno = ENYI; /* Not yet implemented */ return FALSE; }