Wrap everything in struct connection, and enforce timeouts everywhere

(except for DNS operations).  Always use funopen() for HTTP, to support
both timeouts and SSL.
This commit is contained in:
Dag-Erling Smørgrav 2002-06-05 12:19:08 +00:00
parent af53575a04
commit 9601e333a8
4 changed files with 214 additions and 123 deletions

View File

@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <sys/uio.h>
#include <netinet/in.h>
#include <ctype.h> /* XXX */
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>
@ -195,6 +196,22 @@ _fetch_default_proxy_port(const char *scheme)
return (0);
}
/*
* Create a connection for an existing descriptor.
*/
conn_t *
_fetch_reopen(int sd)
{
conn_t *conn;
/* allocate and fill connection structure */
if ((conn = calloc(1, sizeof *conn)) == NULL)
return (NULL);
conn->sd = sd;
return (conn);
}
/*
* Establish a TCP connection to the specified port on the specified host.
*/
@ -241,34 +258,78 @@ _fetch_connect(const char *host, int port, int af, int verbose)
return (NULL);
}
/* allocate and fill connection structure */
if ((conn = calloc(1, sizeof *conn)) == NULL) {
if ((conn = _fetch_reopen(sd)) == NULL)
close(sd);
return (NULL);
}
if ((conn->host = strdup(host)) == NULL) {
free(conn);
close(sd);
return (NULL);
}
conn->port = port;
conn->af = af;
conn->sd = sd;
return (conn);
}
/*
* Read a line of text from a socket w/ timeout
* Read a character from a connection w/ timeout
*/
ssize_t
_fetch_read(conn_t *conn, char *buf, size_t len)
{
struct timeval now, timeout, wait;
fd_set readfds;
ssize_t rlen, total;
int r;
if (fetchTimeout) {
FD_ZERO(&readfds);
gettimeofday(&timeout, NULL);
timeout.tv_sec += fetchTimeout;
}
total = 0;
while (len > 0) {
while (fetchTimeout && !FD_ISSET(conn->sd, &readfds)) {
FD_SET(conn->sd, &readfds);
gettimeofday(&now, NULL);
wait.tv_sec = timeout.tv_sec - now.tv_sec;
wait.tv_usec = timeout.tv_usec - now.tv_usec;
if (wait.tv_usec < 0) {
wait.tv_usec += 1000000;
wait.tv_sec--;
}
if (wait.tv_sec < 0)
return (rlen);
errno = 0;
r = select(conn->sd + 1, &readfds, NULL, NULL, &wait);
if (r == -1) {
if (errno == EINTR && fetchRestartCalls)
continue;
return (-1);
}
}
if (conn->ssl != NULL)
rlen = SSL_read(conn->ssl, buf, len);
else
rlen = read(conn->sd, buf, len);
if (rlen == 0)
break;
if (rlen < 0) {
if (errno == EINTR && fetchRestartCalls)
continue;
return (-1);
}
len -= rlen;
buf += rlen;
total += rlen;
}
return (total);
}
/*
* Read a line of text from a connection w/ timeout
*/
#define MIN_BUF_SIZE 1024
int
_fetch_getln(conn_t *conn)
{
struct timeval now, timeout, wait;
fd_set readfds;
int r;
char *tmp;
size_t tmpsize;
char c;
if (conn->buf == NULL) {
@ -282,50 +343,11 @@ _fetch_getln(conn_t *conn)
conn->buf[0] = '\0';
conn->buflen = 0;
if (fetchTimeout) {
gettimeofday(&timeout, NULL);
timeout.tv_sec += fetchTimeout;
FD_ZERO(&readfds);
}
do {
if (fetchTimeout) {
FD_SET(conn->sd, &readfds);
gettimeofday(&now, NULL);
wait.tv_sec = timeout.tv_sec - now.tv_sec;
wait.tv_usec = timeout.tv_usec - now.tv_usec;
if (wait.tv_usec < 0) {
wait.tv_usec += 1000000;
wait.tv_sec--;
}
if (wait.tv_sec < 0) {
errno = ETIMEDOUT;
return (-1);
}
r = select(conn->sd + 1, &readfds, NULL, NULL, &wait);
if (r == -1) {
if (errno == EINTR && fetchRestartCalls)
continue;
/* EBADF or EINVAL: shouldn't happen */
return (-1);
}
if (!FD_ISSET(conn->sd, &readfds))
continue;
}
r = read(conn->sd, &c, 1);
if (r == 0)
break;
if (r == -1) {
if (errno == EINTR && fetchRestartCalls)
continue;
/* any other error is bad news */
if (_fetch_read(conn, &c, 1) == -1)
return (-1);
}
conn->buf[conn->buflen++] = c;
if (conn->buflen == conn->bufsize) {
char *tmp;
size_t tmpsize;
tmp = conn->buf;
tmpsize = conn->bufsize * 2 + 1;
if ((tmp = realloc(tmp, tmpsize)) == NULL) {
@ -344,25 +366,73 @@ _fetch_getln(conn_t *conn)
/*
* Write a line of text to a socket w/ timeout
* XXX currently does not enforce timeout
* Write to a connection w/ timeout
*/
ssize_t
_fetch_write(conn_t *conn, const char *buf, size_t len)
{
struct timeval now, timeout, wait;
fd_set writefds;
ssize_t wlen, total;
int r;
if (fetchTimeout) {
FD_ZERO(&writefds);
gettimeofday(&timeout, NULL);
timeout.tv_sec += fetchTimeout;
}
while (len > 0) {
while (fetchTimeout && !FD_ISSET(conn->sd, &writefds)) {
FD_SET(conn->sd, &writefds);
gettimeofday(&now, NULL);
wait.tv_sec = timeout.tv_sec - now.tv_sec;
wait.tv_usec = timeout.tv_usec - now.tv_usec;
if (wait.tv_usec < 0) {
wait.tv_usec += 1000000;
wait.tv_sec--;
}
if (wait.tv_sec < 0) {
errno = ETIMEDOUT;
return (-1);
}
errno = 0;
r = select(conn->sd + 1, NULL, &writefds, NULL, &wait);
if (r == -1) {
if (errno == EINTR && fetchRestartCalls)
continue;
return (-1);
}
}
errno = 0;
if (conn->ssl != NULL)
wlen = SSL_write(conn->ssl, buf, len);
else
wlen = write(conn->sd, buf, len);
if (wlen == 0)
/* we consider a short write a failure */
return (-1);
if (wlen < 0) {
if (errno == EINTR && fetchRestartCalls)
continue;
return (-1);
}
len -= wlen;
buf += wlen;
total += wlen;
}
return (total);
}
/*
* Write a line of text to a connection w/ timeout
*/
int
_fetch_putln(conn_t *conn, const char *str, size_t len)
{
struct iovec iov[2];
ssize_t wlen;
/* XXX should enforce timeout */
iov[0].iov_base = (char *)str;
iov[0].iov_len = len;
iov[1].iov_base = (char *)ENDL;
iov[1].iov_len = sizeof ENDL;
len += sizeof ENDL;
wlen = writev(conn->sd, iov, 2);
if (wlen < 0 || (size_t)wlen != len)
if (_fetch_write(conn, str, len) == -1 ||
_fetch_write(conn, ENDL, sizeof ENDL) == -1)
return (-1);
DEBUG(fprintf(stderr, ">>> %.*s\n", (int)len, str));
return (0);
}
@ -376,7 +446,6 @@ _fetch_close(conn_t *conn)
int ret;
ret = close(conn->sd);
free(conn->host);
free(conn);
return (ret);
}

View File

@ -45,15 +45,13 @@
/* Connection */
typedef struct fetchconn conn_t;
struct fetchconn {
char *host; /* host name */
int port; /* port */
int af; /* address family */
int sd; /* socket descriptor */
char *buf; /* buffer */
size_t bufsize; /* buffer size */
size_t buflen; /* length of buffer contents */
int err; /* last protocol reply code */
SSL *ssl_ctx; /* SSL context if needed */
SSL *ssl; /* SSL handle */
SSL_CTX *ssl_ctx; /* SSL context */
X509 *ssl_cert; /* server certificate */
SSL_METHOD *ssl_meth; /* SSL method */
};
@ -71,7 +69,10 @@ void _fetch_info(const char *, ...);
int _fetch_default_port(const char *);
int _fetch_default_proxy_port(const char *);
conn_t *_fetch_connect(const char *, int, int, int);
conn_t *_fetch_reopen(int sd);
ssize_t _fetch_read(conn_t *, char *, size_t);
int _fetch_getln(conn_t *);
ssize_t _fetch_write(conn_t *, const char *, size_t);
int _fetch_putln(conn_t *, const char *, size_t);
int _fetch_close(conn_t *);
int _fetch_add_entry(struct url_ent **, int *, int *,

View File

@ -311,8 +311,8 @@ _ftp_stat(conn_t *conn, const char *file, struct url_stat *us)
* I/O functions for FTP
*/
struct ftpio {
conn_t *conn; /* Control connection */
int dsd; /* Data socket descriptor */
conn_t *cconn; /* Control connection */
conn_t *dconn; /* Data connection */
int dir; /* Direction */
int eof; /* EOF reached */
int err; /* Error code */
@ -334,7 +334,7 @@ _ftp_readfn(void *v, char *buf, int len)
errno = EBADF;
return (-1);
}
if (io->conn == NULL || io->dsd == -1 || io->dir == O_WRONLY) {
if (io->cconn == NULL || io->dconn == NULL || io->dir == O_WRONLY) {
errno = EBADF;
return (-1);
}
@ -344,7 +344,7 @@ _ftp_readfn(void *v, char *buf, int len)
}
if (io->eof)
return (0);
r = read(io->dsd, buf, len);
r = _fetch_read(io->dconn, buf, len);
if (r > 0)
return (r);
if (r == 0) {
@ -367,7 +367,7 @@ _ftp_writefn(void *v, const char *buf, int len)
errno = EBADF;
return (-1);
}
if (io->conn == NULL || io->dsd == -1 || io->dir == O_RDONLY) {
if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) {
errno = EBADF;
return (-1);
}
@ -375,7 +375,7 @@ _ftp_writefn(void *v, const char *buf, int len)
errno = io->err;
return (-1);
}
w = write(io->dsd, buf, len);
w = _fetch_write(io->dconn, buf, len);
if (w >= 0)
return (w);
if (errno != EINTR)
@ -410,30 +410,32 @@ _ftp_closefn(void *v)
}
if (io->dir == -1)
return (0);
if (io->conn == NULL || io->dsd == -1) {
if (io->cconn == NULL || io->dconn == NULL) {
errno = EBADF;
return (-1);
}
close(io->dsd);
_fetch_close(io->dconn);
io->dir = -1;
io->dsd = -1;
io->dconn = NULL;
DEBUG(fprintf(stderr, "Waiting for final status\n"));
r = _ftp_chkerr(io->conn);
_fetch_close(io->conn);
r = _ftp_chkerr(io->cconn);
_fetch_close(io->cconn);
free(io);
return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1;
}
static FILE *
_ftp_setup(conn_t *conn, int dsd, int mode)
_ftp_setup(conn_t *cconn, conn_t *dconn, int mode)
{
struct ftpio *io;
FILE *f;
if (cconn == NULL || dconn == NULL)
return (NULL);
if ((io = malloc(sizeof *io)) == NULL)
return (NULL);
io->conn = conn;
io->dsd = dsd;
io->cconn = cconn;
io->dconn = dconn;
io->dir = mode;
io->eof = io->err = 0;
f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn);
@ -694,7 +696,7 @@ _ftp_transfer(conn_t *conn, const char *oper, const char *file,
sd = d;
}
if ((df = _ftp_setup(conn, sd, mode)) == NULL)
if ((df = _ftp_setup(conn, _fetch_reopen(sd), mode)) == NULL)
goto sysouch;
return (df);

View File

@ -107,10 +107,11 @@ __FBSDID("$FreeBSD$");
struct httpio
{
conn_t *conn; /* connection */
int chunked; /* chunked mode */
char *buf; /* chunk buffer */
size_t b_size; /* size of chunk buffer */
ssize_t b_len; /* amount of data currently in buffer */
int b_pos; /* current read offset in buffer */
size_t bufsize; /* size of chunk buffer */
ssize_t buflen; /* amount of data currently in buffer */
int bufpos; /* current read offset in buffer */
int eof; /* end-of-file flag */
int error; /* error flag */
size_t chunksize; /* remaining size of current chunk */
@ -163,17 +164,43 @@ _http_new_chunk(struct httpio *io)
return (io->chunksize);
}
/*
* Grow the input buffer to at least len bytes
*/
static inline int
_http_growbuf(struct httpio *io, size_t len)
{
char *tmp;
if (io->bufsize >= len)
return (0);
if ((tmp = realloc(io->buf, len)) == NULL)
return (-1);
io->buf = tmp;
io->bufsize = len;
}
/*
* Fill the input buffer, do chunk decoding on the fly
*/
static int
_http_fillbuf(struct httpio *io)
_http_fillbuf(struct httpio *io, size_t len)
{
if (io->error)
return (-1);
if (io->eof)
return (0);
if (io->chunked == 0) {
if (_http_growbuf(io, len) == -1)
return (-1);
if ((io->buflen = _fetch_read(io->conn, io->buf, len)) == -1)
return (-1);
io->bufpos = 0;
return (io->buflen);
}
if (io->chunksize == 0) {
switch (_http_new_chunk(io)) {
case -1:
@ -185,31 +212,25 @@ _http_fillbuf(struct httpio *io)
}
}
if (io->b_size < io->chunksize) {
char *tmp;
if ((tmp = realloc(io->buf, io->chunksize)) == NULL)
return (-1);
io->buf = tmp;
io->b_size = io->chunksize;
}
if ((io->b_len = read(io->conn->sd, io->buf, io->chunksize)) == -1)
if (len > io->chunksize)
len = io->chunksize;
if (_http_growbuf(io, len) == -1)
return (-1);
io->chunksize -= io->b_len;
if ((io->buflen = _fetch_read(io->conn, io->buf, len)) == -1)
return (-1);
io->chunksize -= io->buflen;
if (io->chunksize == 0) {
char endl[2];
if (read(io->conn->sd, &endl[0], 1) == -1 ||
read(io->conn->sd, &endl[1], 1) == -1 ||
if (_fetch_read(io->conn, endl, 2) != 2 ||
endl[0] != '\r' || endl[1] != '\n')
return (-1);
}
io->b_pos = 0;
io->bufpos = 0;
return (io->b_len);
return (io->buflen);
}
/*
@ -228,14 +249,14 @@ _http_readfn(void *v, char *buf, int len)
for (pos = 0; len > 0; pos += l, len -= l) {
/* empty buffer */
if (!io->buf || io->b_pos == io->b_len)
if (_http_fillbuf(io) < 1)
if (!io->buf || io->bufpos == io->buflen)
if (_http_fillbuf(io, len) < 1)
break;
l = io->b_len - io->b_pos;
l = io->buflen - io->bufpos;
if (len < l)
l = len;
bcopy(io->buf + io->b_pos, buf + pos, l);
io->b_pos += l;
bcopy(io->buf + io->bufpos, buf + pos, l);
io->bufpos += l;
}
if (!pos && io->error)
@ -251,7 +272,7 @@ _http_writefn(void *v, const char *buf, int len)
{
struct httpio *io = (struct httpio *)v;
return (write(io->conn->sd, buf, len));
return (_fetch_write(io->conn, buf, len));
}
/*
@ -274,7 +295,7 @@ _http_closefn(void *v)
* Wrap a file descriptor up
*/
static FILE *
_http_funopen(conn_t *conn)
_http_funopen(conn_t *conn, int chunked)
{
struct httpio *io;
FILE *f;
@ -284,6 +305,7 @@ _http_funopen(conn_t *conn)
return (NULL);
}
io->conn = conn;
io->chunked = chunked;
f = funopen(io, _http_readfn, _http_writefn, NULL, _http_closefn);
if (f == NULL) {
_fetch_syserr();
@ -725,6 +747,9 @@ _http_print_html(FILE *out, FILE *in)
/*
* Send a request and process the reply
*
* XXX This function is way too long, the do..while loop should be split
* XXX off into a separate function.
*/
FILE *
_http_request(struct url *URL, const char *op, struct url_stat *us,
@ -1020,13 +1045,7 @@ _http_request(struct url *URL, const char *op, struct url_stat *us,
URL->length = clength;
/* wrap it up in a FILE */
if (chunked) {
f = _http_funopen(conn);
} else {
f = fdopen(dup(conn->sd), "r");
_fetch_close(conn);
}
if (f == NULL) {
if ((f = _http_funopen(conn, chunked)) == NULL) {
_fetch_syserr();
goto ouch;
}