diff options
| author | Dag-Erling Smørgrav <des@FreeBSD.org> | 2000-06-29 08:26:26 +0000 |
|---|---|---|
| committer | Dag-Erling Smørgrav <des@FreeBSD.org> | 2000-06-29 08:26:26 +0000 |
| commit | f325efaaa8dcfcd2bc8942d818fdf18c11a3e439 (patch) | |
| tree | e071e45e160dce1eb55420a7154c5dc4c8011f04 | |
| parent | 358c7ac1dcf9c1633e1f815eec7c09cc5cf0c1c1 (diff) | |
Notes
| -rw-r--r-- | lib/libfetch/Makefile | 17 | ||||
| -rw-r--r-- | lib/libfetch/common.c | 105 | ||||
| -rw-r--r-- | lib/libfetch/common.h | 4 | ||||
| -rw-r--r-- | lib/libfetch/fetch.3 | 109 | ||||
| -rw-r--r-- | lib/libfetch/fetch.c | 142 | ||||
| -rw-r--r-- | lib/libfetch/fetch.h | 37 | ||||
| -rw-r--r-- | lib/libfetch/fetch_err.et | 50 | ||||
| -rw-r--r-- | lib/libfetch/file.c | 16 | ||||
| -rw-r--r-- | lib/libfetch/ftp.c | 357 | ||||
| -rw-r--r-- | lib/libfetch/ftp.errors | 3 | ||||
| -rw-r--r-- | lib/libfetch/http.c | 353 |
11 files changed, 863 insertions, 330 deletions
diff --git a/lib/libfetch/Makefile b/lib/libfetch/Makefile index 21d615abadca..b85f402ddfc1 100644 --- a/lib/libfetch/Makefile +++ b/lib/libfetch/Makefile @@ -3,16 +3,17 @@ MAINTAINER= des@freebsd.org LIB= fetch CFLAGS+= -I. -Wall -pedantic +CFLAGS+= -DINET6 .if !defined(DEBUG) CFLAGS+= -DNDEBUG .endif -SRCS= fetch.c common.c ftp.c http.c file.c fetch_err.c \ - fetch_err.h ftperr.h httperr.h -INCS= fetch.h ${.OBJDIR}/fetch_err.h +SRCS= fetch.c common.c ftp.c http.c file.c \ + ftperr.h httperr.h +INCS= fetch.h MAN3= fetch.3 -CLEANFILES= fetch_err.c fetch_err.h ftperr.h httperr.h +CLEANFILES= ftperr.h httperr.h -SHLIB_MAJOR= 1 +SHLIB_MAJOR= 2 SHLIB_MINOR= 0 ftperr.h: ftp.errors @@ -37,10 +38,4 @@ httperr.h: http.errors @echo " { -1, FETCH_UNKNOWN, \"Unknown HTTP error\" }" >> ${.TARGET} @echo "};" >> ${.TARGET} -hdrs: fetch_err.h - -.ORDER: fetch_err.c fetch_err.h -fetch_err.c fetch_err.h: fetch_err.et - compile_et ${.ALLSRC} - .include <bsd.lib.mk> diff --git a/lib/libfetch/common.c b/lib/libfetch/common.c index ed5072ce1c3c..59f92cbc14ed 100644 --- a/lib/libfetch/common.c +++ b/lib/libfetch/common.c @@ -33,9 +33,9 @@ #include <sys/time.h> #include <netinet/in.h> -#include <com_err.h> #include <errno.h> #include <netdb.h> +#include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -51,38 +51,25 @@ * Error messages for resolver errors */ static struct fetcherr _netdb_errlist[] = { - { HOST_NOT_FOUND, FETCH_RESOLV, "Host not found" }, - { TRY_AGAIN, FETCH_TEMP, "Transient resolver failure" }, - { NO_RECOVERY, FETCH_RESOLV, "Non-recoverable resolver failure" }, - { NO_DATA, FETCH_RESOLV, "No address record" }, + { EAI_NODATA, FETCH_RESOLV, "Host not found" }, + { EAI_AGAIN, FETCH_TEMP, "Transient resolver failure" }, + { EAI_FAIL, FETCH_RESOLV, "Non-recoverable resolver failure" }, + { EAI_NONAME, FETCH_RESOLV, "No address record" }, { -1, FETCH_UNKNOWN, "Unknown resolver error" } }; -static int com_err_initialized; /*** Error-reporting functions ***********************************************/ /* - * Initialize the common error library - */ -static void -_fetch_init_com_err(void) -{ - initialize_ftch_error_table(); - com_err_initialized = 1; -} - -/* * Map error code to string */ -static int +static struct fetcherr * _fetch_finderr(struct fetcherr *p, int e) { - int i; - for (i = 0; p[i].num != -1; i++) - if (p[i].num == e) - break; - return i; + while (p->num != -1 && p->num != e) + p++; + return p; } /* @@ -91,14 +78,9 @@ _fetch_finderr(struct fetcherr *p, int e) void _fetch_seterr(struct fetcherr *p, int e) { - int n; - - if (!com_err_initialized) - _fetch_init_com_err(); - - n = _fetch_finderr(p, e); - fetchLastErrCode = p[n].cat; - com_err("libfetch", fetchLastErrCode, "(%03d %s)", e, p[n].string); + p = _fetch_finderr(p, e); + fetchLastErrCode = p->cat; + snprintf(fetchLastErrString, MAXERRSTRING, "%s", p->string); } /* @@ -110,9 +92,6 @@ _fetch_syserr(void) int e; e = errno; - if (!com_err_initialized) - _fetch_init_com_err(); - switch (errno) { case 0: fetchLastErrCode = FETCH_OK; @@ -163,34 +142,22 @@ _fetch_syserr(void) default: fetchLastErrCode = FETCH_UNKNOWN; } - com_err("libfetch", fetchLastErrCode, "(%03d %s)", e, strerror(e)); + snprintf(fetchLastErrString, MAXERRSTRING, "%s", strerror(e)); } /* * Emit status message */ -int +void _fetch_info(char *fmt, ...) { va_list ap; - char *s; - if (!com_err_initialized) - _fetch_init_com_err(); - va_start(ap, fmt); - vasprintf(&s, fmt, ap); + vfprintf(stderr, fmt, ap); va_end(ap); - - if (s == NULL) { - com_err("libfetch", FETCH_MEMORY, ""); - return -1; - } else { - com_err("libfetch", FETCH_VERBOSE, "%s", s); - free(s); - return 0; - } + fputc('\n', stderr); } @@ -200,11 +167,11 @@ _fetch_info(char *fmt, ...) * Establish a TCP connection to the specified port on the specified host. */ int -_fetch_connect(char *host, int port, int verbose) +_fetch_connect(char *host, int port, int af, int verbose) { - struct sockaddr_in sin; - struct hostent *he; - int sd; + char pbuf[10]; + struct addrinfo hints, *res, *res0; + int sd, err; #ifndef NDEBUG fprintf(stderr, "\033[1m---> %s:%d\033[m\n", host, port); @@ -213,29 +180,33 @@ _fetch_connect(char *host, int port, int verbose) if (verbose) _fetch_info("looking up %s", host); - /* look up host name */ - if ((he = gethostbyname(host)) == NULL) { - _netdb_seterr(h_errno); + /* look up host name and set up socket address structure */ + snprintf(pbuf, sizeof(pbuf), "%d", port); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + if ((err = getaddrinfo(host, pbuf, &hints, &res0)) != 0) { + _netdb_seterr(err); return -1; } if (verbose) _fetch_info("connecting to %s:%d", host, port); - /* set up socket address structure */ - bzero(&sin, sizeof(sin)); - bcopy(he->h_addr, (char *)&sin.sin_addr, he->h_length); - sin.sin_family = he->h_addrtype; - sin.sin_port = htons(port); - /* try to connect */ - if ((sd = socket(sin.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { - _fetch_syserr(); - return -1; + sd = -1; + for (res = res0; res; res = res->ai_next) { + if ((sd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) < 0) + continue; + if (connect(sd, res->ai_addr, res->ai_addrlen) >= 0) + break; + close(sd); + sd = -1; } - if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1) { + if (sd < 0) { _fetch_syserr(); - close(sd); return -1; } diff --git a/lib/libfetch/common.h b/lib/libfetch/common.h index 1762a00ecf12..79bd54d761e8 100644 --- a/lib/libfetch/common.h +++ b/lib/libfetch/common.h @@ -40,8 +40,8 @@ struct fetcherr { void _fetch_seterr(struct fetcherr *p, int e); void _fetch_syserr(void); -int _fetch_info(char *fmt, ...); -int _fetch_connect(char *host, int port, int verbose); +void _fetch_info(char *fmt, ...); +int _fetch_connect(char *host, int port, int af, int verbose); int _fetch_getln(int fd, char **buf, size_t *size, size_t *len); int _fetch_add_entry(struct url_ent **p, int *size, int *len, char *name, struct url_stat *stat); diff --git a/lib/libfetch/fetch.3 b/lib/libfetch/fetch.3 index 28bdb3336d6d..4b6af268ea3c 100644 --- a/lib/libfetch/fetch.3 +++ b/lib/libfetch/fetch.3 @@ -28,11 +28,13 @@ .Dt FETCH 3 .Os .Sh NAME +.Nm fetchMakeURL , +.Nm fetchParseURL , +.Nm fetchFreeURL , .Nm fetchGetURL , .Nm fetchPutURL , .Nm fetchStatURL , .Nm fetchListURL , -.Nm fetchParseURL , .Nm fetchGet , .Nm fetchPut , .Nm fetchStat , @@ -56,6 +58,12 @@ .Fd #include <sys/param.h> .Fd #include <stdio.h> .Fd #include <fetch.h> +.Ft struct url * +.Fn fetchMakeURL "char *scheme" "char *host" "int port" "char *doc" "char *user" "char *pwd" +.Ft struct url * +.Fn fetchParseURL "char *URL" +.Ft void +.Fn fetchFreeURL "struct url *URL" .Ft FILE * .Fn fetchGetURL "char *URL" "char *flags" .Ft FILE * @@ -64,8 +72,6 @@ .Fn fetchStatURL "char *URL" "struct url_stat *us" "char *flags" .Ft struct url_ent * .Fn fetchListURL "char *URL" "char *flags" -.Ft struct url * -.Fn fetchParseURL "char *URL" .Ft FILE * .Fn fetchGet "struct url *URL" "char *flags" .Ft FILE * @@ -103,6 +109,51 @@ These functions implement a high-level library for retrieving and uploading files using Uniform Resource Locators (URLs). .Pp +.Fn fetchParseURL +takes a URL in the form of a null-terminated string and splits it into +its components function according to the Common Internet Scheme Syntax +detailed in RFC1738. +A regular expression which produces this syntax is: +.Bd -literal + <scheme>:(//(<user>(:<pwd>)?@)?<host>(:<port>)?)?/(<document>)? +.Ed +.Pp +Note that some components of the URL are not necessarily relevant to +all URL schemes. +For instance, the file scheme only needs the <scheme> +and <document> components. +.Pp +.Fn fetchMakeURL +and +.Fn fetchParseURL +return a pointer to a +.Fa url +structure, which is defined as follows in +.Aq Pa fetch.h : +.Bd -literal +#define URL_SCHEMELEN 16 +#define URL_USERLEN 256 +#define URL_PWDLEN 256 + +struct url { + char scheme[URL_SCHEMELEN+1]; + char user[URL_USERLEN+1]; + char pwd[URL_PWDLEN+1]; + char host[MAXHOSTNAMELEN+1]; + int port; + char *doc; + off_t offset; + size_t length; +}; +.Ed +.Pp +The pointer returned by +.Fn fetchMakeURL +or +.Fn fetchParseURL +should be freed using +.Fn fetchFreeURL . +.Pp .Fn fetchGetURL and .Fn fetchPutURL @@ -134,6 +185,16 @@ struct url_stat { }; .Ed .Pp +If the size could not be obtained from the server, the +.Fa size +field is set to -1. +If the modification time could not be obtained from the server, the +.Fa mtime +field is set to the epoch. +If the access time could not be obtained from the server, the +.Fa atime +field is set to the modification time. +.Pp .Fn fetchListURL attempts to list the contents of the directory pointed to by the URL provided. @@ -158,25 +219,6 @@ The pointer returned by should be freed using .Fn free . .Pp -.Fn fetchParseURL -takes a URL in the form of a null-terminated string and splits it into -its components function according to the Common Internet Scheme Syntax -detailed in RFC1738. A regular expression which produces this syntax -is: -.Bd -literal - <scheme>:(//(<user>(:<pwd>)?@)?<host>(:<port>)?)?/(<document>)? -.Ed -.Pp -Note that some components of the URL are not necessarily relevant to -all URL schemes. -For instance, the file scheme only needs the <scheme> -and <document> components. -.Pp -The pointer returned by -.Fn fetchParseURL -should be freed using -.Fn free . -.Pp .Fn fetchGet , .Fn fetchPut and @@ -293,13 +335,8 @@ functions return 0 on success and -1 on failure. All other functions return a stream pointer which may be used to access the requested document, or NULL if an error occurred. .Pp -.Nm Libfetch -uses the Common Error Library -.Nm ( libcom_err ) -to report errors. -The error code passed to -.Fn com_err -is one of: +The following error codes are defined in +.Aq Pa fetch.h : .Bl -tag -width 18n .It Bq Er FETCH_ABORT Operation aborted @@ -349,7 +386,6 @@ and environment variables, respectively, as the address of a proxy server to use for transferring files. .Sh SEE ALSO -.Xr com_err 3 , .Xr fetch 1 , .Xr ftpio 3 , .Xr ip 4 . @@ -378,14 +414,6 @@ to use for transferring files. .%B File Transfer Protocol .%O RFC959 .Re -.Sh NOTES -The -.Nm fetch -library uses the Common Error library, and applications which link -with -.Nm libfetch -must therefore also link with -.Nm libcom_err . .Sh HISTORY The .Nm fetch @@ -414,7 +442,6 @@ Some parts of the library are not yet implemented. The most notable examples of this are .Fn fetchPutHTTP , -.Fn fetchStatHTTP , .Fn fetchListHTTP , .Fn fetchListFTP and FTP proxy support. @@ -459,4 +486,8 @@ The HTTP code needs a complete rewrite, or at least a serious cleanup. .Pp The man page is poorly written and produces badly formatted text. .Pp +The error reporting mechanism is unsatisfactory. +.Pp +Some parts of the code are not fully reentrant. +.Pp Tons of other stuff. diff --git a/lib/libfetch/fetch.c b/lib/libfetch/fetch.c index 3c944a189be7..5d83db5b5de8 100644 --- a/lib/libfetch/fetch.c +++ b/lib/libfetch/fetch.c @@ -40,8 +40,9 @@ #include "common.h" -int fetchLastErrCode; -int fetchTimeout; +int fetchLastErrCode; +char fetchLastErrString[MAXERRSTRING]; +int fetchTimeout; /*** Local data **************************************************************/ @@ -69,13 +70,19 @@ static struct fetcherr _url_errlist[] = { FILE * fetchGet(struct url *URL, char *flags) { + int direct; + + direct = (flags && strchr(flags, 'd')); if (strcasecmp(URL->scheme, "file") == 0) return fetchGetFile(URL, flags); else if (strcasecmp(URL->scheme, "http") == 0) return fetchGetHTTP(URL, flags); - else if (strcasecmp(URL->scheme, "ftp") == 0) + else if (strcasecmp(URL->scheme, "ftp") == 0) { + if (!direct && + getenv("FTP_PROXY") == NULL && getenv("HTTP_PROXY") != NULL) + return fetchGetHTTP(URL, flags); return fetchGetFTP(URL, flags); - else { + } else { _url_seterr(URL_BAD_SCHEME); return NULL; } @@ -88,13 +95,19 @@ fetchGet(struct url *URL, char *flags) FILE * fetchPut(struct url *URL, char *flags) { + int direct; + + direct = (flags && strchr(flags, 'd')); if (strcasecmp(URL->scheme, "file") == 0) return fetchPutFile(URL, flags); else if (strcasecmp(URL->scheme, "http") == 0) return fetchPutHTTP(URL, flags); - else if (strcasecmp(URL->scheme, "ftp") == 0) + else if (strcasecmp(URL->scheme, "ftp") == 0) { + if (!direct && + getenv("FTP_PROXY") == NULL && getenv("HTTP_PROXY") != NULL) + return fetchPutHTTP(URL, flags); return fetchPutFTP(URL, flags); - else { + } else { _url_seterr(URL_BAD_SCHEME); return NULL; } @@ -107,13 +120,19 @@ fetchPut(struct url *URL, char *flags) int fetchStat(struct url *URL, struct url_stat *us, char *flags) { + int direct; + + direct = (flags && strchr(flags, 'd')); if (strcasecmp(URL->scheme, "file") == 0) return fetchStatFile(URL, us, flags); else if (strcasecmp(URL->scheme, "http") == 0) return fetchStatHTTP(URL, us, flags); - else if (strcasecmp(URL->scheme, "ftp") == 0) + else if (strcasecmp(URL->scheme, "ftp") == 0) { + if (!direct && + getenv("FTP_PROXY") == NULL && getenv("HTTP_PROXY") != NULL) + return fetchStatHTTP(URL, us, flags); return fetchStatFTP(URL, us, flags); - else { + } else { _url_seterr(URL_BAD_SCHEME); return -1; } @@ -126,13 +145,19 @@ fetchStat(struct url *URL, struct url_stat *us, char *flags) struct url_ent * fetchList(struct url *URL, char *flags) { + int direct; + + direct = (flags && strchr(flags, 'd')); if (strcasecmp(URL->scheme, "file") == 0) return fetchListFile(URL, flags); else if (strcasecmp(URL->scheme, "http") == 0) return fetchListHTTP(URL, flags); - else if (strcasecmp(URL->scheme, "ftp") == 0) + else if (strcasecmp(URL->scheme, "ftp") == 0) { + if (!direct && + getenv("FTP_PROXY") == NULL && getenv("HTTP_PROXY") != NULL) + return fetchListHTTP(URL, flags); return fetchListFTP(URL, flags); - else { + } else { _url_seterr(URL_BAD_SCHEME); return NULL; } @@ -152,7 +177,7 @@ fetchGetURL(char *URL, char *flags) f = fetchGet(u, flags); - free(u); + fetchFreeURL(u); return f; } @@ -171,7 +196,7 @@ fetchPutURL(char *URL, char *flags) f = fetchPut(u, flags); - free(u); + fetchFreeURL(u); return f; } @@ -189,7 +214,7 @@ fetchStatURL(char *URL, struct url_stat *us, char *flags) s = fetchStat(u, us, flags); - free(u); + fetchFreeURL(u); return s; } @@ -207,11 +232,53 @@ fetchListURL(char *URL, char *flags) ue = fetchList(u, flags); - free(u); + fetchFreeURL(u); return ue; } /* + * Make a URL + */ +struct url * +fetchMakeURL(char *scheme, char *host, int port, char *doc, + char *user, char *pwd) +{ + struct url *u; + + if (!scheme || (!host && !doc)) { + _url_seterr(URL_MALFORMED); + return NULL; + } + + if (port < 0 || port > 65535) { + _url_seterr(URL_BAD_PORT); + return NULL; + } + + /* allocate struct url */ + if ((u = calloc(1, sizeof *u)) == NULL) { + _fetch_syserr(); + return NULL; + } + + if ((u->doc = strdup(doc ? doc : "/")) == NULL) { + _fetch_syserr(); + free(u); + return NULL; + } + +#define seturl(x) snprintf(u->x, sizeof u->x, "%s", x) + seturl(scheme); + seturl(host); + seturl(user); + seturl(pwd); +#undef seturl + u->port = port; + + return u; +} + +/* * Split an URL into components. URL syntax is: * method:[//[user[:pwd]@]host[:port]]/[document] * This almost, but not quite, RFC1738 URL syntax. @@ -224,8 +291,7 @@ fetchParseURL(char *URL) int i; /* allocate struct url */ - if ((u = calloc(1, sizeof(struct url))) == NULL) { - errno = ENOMEM; + if ((u = calloc(1, sizeof *u)) == NULL) { _fetch_syserr(); return NULL; } @@ -262,9 +328,18 @@ fetchParseURL(char *URL) } else p = URL; /* hostname */ - for (i = 0; *p && (*p != '/') && (*p != ':'); p++) - if (i < MAXHOSTNAMELEN) - u->host[i++] = *p; +#ifdef INET6 + if (*p == '[' && (q = strchr(p + 1, ']')) != NULL && + (*++q == '\0' || *q == '/' || *q == ':')) { + if ((i = q - p - 2) > MAXHOSTNAMELEN) + i = MAXHOSTNAMELEN; + strncpy(u->host, ++p, i); + p = q; + } else +#endif + for (i = 0; *p && (*p != '/') && (*p != ':'); p++) + if (i < MAXHOSTNAMELEN) + u->host[i++] = *p; /* port */ if (*p == ':') { @@ -282,19 +357,12 @@ fetchParseURL(char *URL) nohost: /* document */ - if (*p) { - struct url *t; - t = realloc(u, sizeof(*u)+strlen(p)-1); - if (t == NULL) { - errno = ENOMEM; - _fetch_syserr(); - goto ouch; - } - u = t; - strcpy(u->doc, p); - } else { - u->doc[0] = '/'; - u->doc[1] = 0; + if (!*p) + p = "/"; + + if ((u->doc = strdup(p)) == NULL) { + _fetch_syserr(); + goto ouch; } DEBUG(fprintf(stderr, @@ -313,3 +381,13 @@ ouch: free(u); return NULL; } + +/* + * Free a URL + */ +void +fetchFreeURL(struct url *u) +{ + free(u->doc); + free(u); +} diff --git a/lib/libfetch/fetch.h b/lib/libfetch/fetch.h index 23667911b993..2395220af8ef 100644 --- a/lib/libfetch/fetch.h +++ b/lib/libfetch/fetch.h @@ -31,9 +31,7 @@ #ifndef _FETCH_H_INCLUDED #define _FETCH_H_INCLUDED -#include <fetch_err.h> - -#define _LIBFETCH_VER "libfetch/1.0" +#define _LIBFETCH_VER "libfetch/2.0" #define URL_SCHEMELEN 16 #define URL_USERLEN 256 @@ -45,7 +43,9 @@ struct url { char pwd[URL_PWDLEN+1]; char host[MAXHOSTNAMELEN+1]; int port; - char doc[2]; + char *doc; + off_t offset; + size_t length; }; struct url_stat { @@ -59,6 +59,27 @@ struct url_ent { struct url_stat stat; }; +/* Error codes */ +#define FETCH_ABORT 1 +#define FETCH_AUTH 2 +#define FETCH_DOWN 3 +#define FETCH_EXISTS 4 +#define FETCH_FULL 5 +#define FETCH_INFO 6 +#define FETCH_MEMORY 7 +#define FETCH_MOVED 8 +#define FETCH_NETWORK 9 +#define FETCH_OK 10 +#define FETCH_PROTO 11 +#define FETCH_RESOLV 12 +#define FETCH_SERVER 13 +#define FETCH_TEMP 14 +#define FETCH_TIMEOUT 15 +#define FETCH_UNAVAIL 16 +#define FETCH_UNKNOWN 17 +#define FETCH_URL 18 +#define FETCH_VERBOSE 19 + /* FILE-specific functions */ FILE *fetchGetFile(struct url *, char *); FILE *fetchPutFile(struct url *, char *); @@ -79,7 +100,6 @@ int fetchStatFTP(struct url *, struct url_stat *, char *); struct url_ent *fetchListFTP(struct url *, char *); /* Generic functions */ -struct url *fetchParseURL(char *); FILE *fetchGetURL(char *, char *); FILE *fetchPutURL(char *, char *); int fetchStatURL(char *, struct url_stat *, char *); @@ -89,8 +109,15 @@ FILE *fetchPut(struct url *, char *); int fetchStat(struct url *, struct url_stat *, char *); struct url_ent *fetchList(struct url *, char *); +/* URL parsing */ +struct url *fetchMakeURL(char *, char *, int, char *, char *, char *); +struct url *fetchParseURL(char *); +void fetchFreeURL(struct url *); + /* Last error code */ extern int fetchLastErrCode; +#define MAXERRSTRING 256 +extern char fetchLastErrString[MAXERRSTRING]; extern int fetchTimeout; #endif diff --git a/lib/libfetch/fetch_err.et b/lib/libfetch/fetch_err.et deleted file mode 100644 index efaef740139d..000000000000 --- a/lib/libfetch/fetch_err.et +++ /dev/null @@ -1,50 +0,0 @@ -#- -# Copyright (c) 1998 Dag-Erling Coïdan Smørgrav -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer -# in this position and unchanged. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# $FreeBSD$ -# -et ftch - ec FETCH_ABORT, "Operation aborted" - ec FETCH_AUTH, "Authentication failed" - ec FETCH_DOWN, "Service unavailable" - ec FETCH_EXISTS, "File exists" - ec FETCH_FULL, "File system full" - ec FETCH_INFO, "Informational response" - ec FETCH_MEMORY, "Insufficient memory" - ec FETCH_MOVED, "File has moved" - ec FETCH_NETWORK, "Network error" - ec FETCH_OK, "No error" - ec FETCH_PROTO, "Protocol error" - ec FETCH_RESOLV, "Resolver error" - ec FETCH_SERVER, "Server error" - ec FETCH_TEMP, "Temporary error" - ec FETCH_TIMEOUT, "Operation timed out" - ec FETCH_UNAVAIL, "File is not available" - ec FETCH_UNKNOWN, "Unknown error" - ec FETCH_URL, "Invalid URL" - ec FETCH_VERBOSE, "Info:" -end diff --git a/lib/libfetch/file.c b/lib/libfetch/file.c index d00f2d654959..0da44c9d5fa3 100644 --- a/lib/libfetch/file.c +++ b/lib/libfetch/file.c @@ -47,6 +47,12 @@ fetchGetFile(struct url *u, char *flags) if (f == NULL) _fetch_syserr(); + + if (u->offset && fseek(f, u->offset, SEEK_SET) == -1) { + fclose(f); + _fetch_syserr(); + } + return f; } @@ -58,10 +64,16 @@ fetchPutFile(struct url *u, char *flags) if (flags && strchr(flags, 'a')) f = fopen(u->doc, "a"); else - f = fopen(u->doc, "w"); + f = fopen(u->doc, "w+"); if (f == NULL) _fetch_syserr(); + + if (u->offset && fseek(f, u->offset, SEEK_SET) == -1) { + fclose(f); + _fetch_syserr(); + } + return f; } @@ -70,6 +82,8 @@ _fetch_stat_file(char *fn, struct url_stat *us) { struct stat sb; + us->size = -1; + us->atime = us->mtime = 0; if (stat(fn, &sb) == -1) { _fetch_syserr(); return -1; diff --git a/lib/libfetch/ftp.c b/lib/libfetch/ftp.c index 0f45bfadafd7..c9aec194fe75 100644 --- a/lib/libfetch/ftp.c +++ b/lib/libfetch/ftp.c @@ -62,6 +62,7 @@ #include <ctype.h> #include <errno.h> +#include <netdb.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -82,10 +83,13 @@ #define FTP_FILE_STATUS 213 #define FTP_SERVICE_READY 220 #define FTP_PASSIVE_MODE 227 +#define FTP_LPASSIVE_MODE 228 +#define FTP_EPASSIVE_MODE 229 #define FTP_LOGGED_IN 230 #define FTP_FILE_ACTION_OK 250 #define FTP_NEED_PASSWORD 331 #define FTP_NEED_ACCOUNT 332 +#define FTP_FILE_OK 350 #define FTP_SYNTAX_ERROR 500 static char ENDL[2] = "\r\n"; @@ -98,25 +102,56 @@ static size_t lr_size, lr_length; static int last_code; #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ - && isdigit(foo[2]) && foo[3] == ' ') + && isdigit(foo[2]) \ + && (foo[3] == ' ' || foo[3] == '\0')) #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ && isdigit(foo[2]) && foo[3] == '-') +/* translate IPv4 mapped IPv6 address to IPv4 address */ +static void +unmappedaddr(struct sockaddr_in6 *sin6) +{ + struct sockaddr_in *sin4; + u_int32_t addr; + int port; + + if (sin6->sin6_family != AF_INET6 || + !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + return; + sin4 = (struct sockaddr_in *)sin6; + addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; + port = sin6->sin6_port; + memset(sin4, 0, sizeof(struct sockaddr_in)); + sin4->sin_addr.s_addr = addr; + sin4->sin_port = port; + sin4->sin_family = AF_INET; + sin4->sin_len = sizeof(struct sockaddr_in); +} + /* * Get server response */ static int _ftp_chkerr(int cd) { - do { - if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { - _fetch_syserr(); - return -1; - } + if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { + _fetch_syserr(); + return -1; + } #ifndef NDEBUG - _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); + _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); #endif - } while (isftpinfo(last_reply)); + if (isftpinfo(last_reply)) { + while (!isftpreply(last_reply)) { + if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { + _fetch_syserr(); + return -1; + } +#ifndef NDEBUG + _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); +#endif + } + } while (lr_length && isspace(last_reply[lr_length-1])) lr_length--; @@ -160,7 +195,7 @@ _ftp_cmd(int cd, char *fmt, ...) iov[0].iov_base = msg; iov[0].iov_len = strlen(msg); iov[1].iov_base = ENDL; - iov[1].iov_len = sizeof(ENDL); + iov[1].iov_len = sizeof ENDL; r = writev(cd, iov, 2); free(msg); if (r == -1) { @@ -175,9 +210,12 @@ _ftp_cmd(int cd, char *fmt, ...) * Transfer file */ static FILE * -_ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) +_ftp_transfer(int cd, char *oper, char *file, + char *mode, off_t offset, char *flags) { - struct sockaddr_in sin; + struct sockaddr_storage sin; + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin4; int pasv, high, verbose; int e, sd = -1; socklen_t l; @@ -189,6 +227,10 @@ _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) high = (flags && strchr(flags, 'h')); verbose = (flags && strchr(flags, 'v')); + /* passive mode */ + if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL) + pasv = (strncasecmp(s, "no", 2) != 0); + /* change directory */ if (((s = strrchr(file, '/')) != NULL) && (s != file)) { *s = 0; @@ -213,48 +255,121 @@ _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) /* s now points to file name */ + /* find our own address, bind, and listen */ + l = sizeof sin; + if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) + goto sysouch; + if (sin.ss_family == AF_INET6) + unmappedaddr((struct sockaddr_in6 *)&sin); + /* open data socket */ - if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { _fetch_syserr(); return NULL; } if (pasv) { - u_char addr[6]; + u_char addr[64]; char *ln, *p; int i; + int port; /* send PASV command */ if (verbose) _fetch_info("setting passive mode"); - if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) + switch (sin.ss_family) { + case AF_INET: + if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) + goto ouch; + break; + case AF_INET6: + if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { + if (e == -1) + goto ouch; + if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) + goto ouch; + } + break; + default: + e = 999; /* XXX: error code should be prepared */ goto ouch; + } /* * Find address and port number. The reply to the PASV command * is IMHO the one and only weak point in the FTP protocol. */ ln = last_reply; - for (p = ln + 3; !isdigit(*p); p++) + for (p = ln + 3; *p && *p != '('; p++) /* nothing */ ; - for (p--, i = 0; i < 6; i++) { - p++; /* skip the comma */ - addr[i] = strtol(p, &p, 10); + if (!*p) { + e = 999; + goto ouch; + } + p++; + switch (e) { + case FTP_PASSIVE_MODE: + case FTP_LPASSIVE_MODE: + l = (e == FTP_PASSIVE_MODE ? 6 : 21); + for (i = 0; *p && i < l; i++, p++) + addr[i] = strtol(p, &p, 10); + if (i < l) { + e = 999; + goto ouch; + } + break; + case FTP_EPASSIVE_MODE: + if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], + &port, &addr[3]) != 5 || + addr[0] != addr[1] || + addr[0] != addr[2] || addr[0] != addr[3]) { + e = 999; + goto ouch; + } + break; } + /* seek to required offset */ + if (offset) + if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) + goto sysouch; + /* construct sockaddr for data socket */ - l = sizeof(sin); + l = sizeof sin; if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) goto sysouch; - bcopy(addr, (char *)&sin.sin_addr, 4); - bcopy(addr + 4, (char *)&sin.sin_port, 2); + if (sin.ss_family == AF_INET6) + unmappedaddr((struct sockaddr_in6 *)&sin); + switch (sin.ss_family) { + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&sin; + if (e == FTP_EPASSIVE_MODE) + sin6->sin6_port = htons(port); + else { + bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); + bcopy(addr + 19, (char *)&sin6->sin6_port, 2); + } + break; + case AF_INET: + sin4 = (struct sockaddr_in *)&sin; + if (e == FTP_EPASSIVE_MODE) + sin4->sin_port = htons(port); + else { + bcopy(addr, (char *)&sin4->sin_addr, 4); + bcopy(addr + 4, (char *)&sin4->sin_port, 2); + } + break; + default: + e = 999; /* XXX: error code should be prepared */ + break; + } /* connect to data port */ if (verbose) _fetch_info("opening data connection"); - if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) + if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) goto sysouch; - + /* make the server initiate the transfer */ if (verbose) _fetch_info("initiating transfer"); @@ -266,19 +381,30 @@ _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) u_int32_t a; u_short p; int arg, d; + char *ap; + char hname[INET6_ADDRSTRLEN]; - /* find our own address, bind, and listen */ - l = sizeof(sin); - if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) - goto sysouch; - sin.sin_port = 0; - arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; - if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, - (char *)&arg, sizeof(arg)) == -1) - goto sysouch; + switch (sin.ss_family) { + case AF_INET6: + ((struct sockaddr_in6 *)&sin)->sin6_port = 0; +#ifdef IPV6_PORTRANGE + arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; + if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, + (char *)&arg, sizeof(arg)) == -1) + goto sysouch; +#endif + break; + case AF_INET: + ((struct sockaddr_in *)&sin)->sin_port = 0; + arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; + if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, + (char *)&arg, sizeof arg) == -1) + goto sysouch; + break; + } if (verbose) _fetch_info("binding data socket"); - if (bind(sd, (struct sockaddr *)&sin, l) == -1) + if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) goto sysouch; if (listen(sd, 1) == -1) goto sysouch; @@ -286,12 +412,46 @@ _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) /* find what port we're on and tell the server */ if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) goto sysouch; - a = ntohl(sin.sin_addr.s_addr); - p = ntohs(sin.sin_port); - e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", - (a >> 24) & 0xff, (a >> 16) & 0xff, - (a >> 8) & 0xff, a & 0xff, - (p >> 8) & 0xff, p & 0xff); + switch (sin.ss_family) { + case AF_INET: + sin4 = (struct sockaddr_in *)&sin; + a = ntohl(sin4->sin_addr.s_addr); + p = ntohs(sin4->sin_port); + e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", + (a >> 24) & 0xff, (a >> 16) & 0xff, + (a >> 8) & 0xff, a & 0xff, + (p >> 8) & 0xff, p & 0xff); + break; + case AF_INET6: +#define UC(b) (((int)b)&0xff) + e = -1; + sin6 = (struct sockaddr_in6 *)&sin; + if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, + hname, sizeof(hname), + NULL, 0, NI_NUMERICHOST) == 0) { + e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, + htons(sin6->sin6_port)); + if (e == -1) + goto ouch; + } + if (e != FTP_OK) { + ap = (char *)&sin6->sin6_addr; + e = _ftp_cmd(cd, + "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + 6, 16, + UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), + UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), + UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), + UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), + 2, + (ntohs(sin6->sin6_port) >> 8) & 0xff, + ntohs(sin6->sin6_port) & 0xff); + } + break; + default: + e = 999; /* XXX: error code should be prepared */ + goto ouch; + } if (e != FTP_OK) goto ouch; @@ -315,13 +475,15 @@ _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) sysouch: _fetch_syserr(); - close(sd); + if (sd >= 0) + close(sd); return NULL; ouch: if (e != -1) _ftp_seterr(e); - close(sd); + if (sd >= 0) + close(sd); return NULL; } @@ -331,26 +493,66 @@ ouch: static int _ftp_connect(char *host, int port, char *user, char *pwd, char *flags) { - int cd, e, pp = FTP_DEFAULT_PORT, direct, verbose; + int cd, e, pp = 0, direct, verbose; +#ifdef INET6 + int af = AF_UNSPEC; +#else + int af = AF_INET; +#endif char *p, *q; + const char *logname; + char localhost[MAXHOSTNAMELEN]; + char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; direct = (flags && strchr(flags, 'd')); verbose = (flags && strchr(flags, 'v')); - + if ((flags && strchr(flags, '4'))) + af = AF_INET; + else if ((flags && strchr(flags, '6'))) + af = AF_INET6; + /* check for proxy */ if (!direct && (p = getenv("FTP_PROXY")) != NULL) { - if ((q = strchr(p, ':')) != NULL) { - /* XXX check that it's a valid number */ + char c = 0; + +#ifdef INET6 + if (*p != '[' || (q = strchr(p + 1, ']')) == NULL || + (*++q != '\0' && *q != ':')) +#endif + q = strchr(p, ':'); + if (q != NULL && *q == ':') { + if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { + /* XXX we should emit some kind of warning */ + } pp = atoi(q+1); + if (pp < 1 || pp > 65535) { + /* XXX we should emit some kind of warning */ + } } - if (q) + if (!pp) { + struct servent *se; + + if ((se = getservbyname("ftp", "tcp")) != NULL) + pp = ntohs(se->s_port); + else + pp = FTP_DEFAULT_PORT; + } + if (q) { +#ifdef INET6 + if (q > p && *p == '[' && *(q - 1) == ']') { + p++; + q--; + } +#endif + c = *q; *q = 0; - cd = _fetch_connect(p, pp, verbose); + } + cd = _fetch_connect(p, pp, af, verbose); if (q) - *q = ':'; + *q = c; } else { /* no proxy, go straight to target */ - cd = _fetch_connect(host, port, verbose); + cd = _fetch_connect(host, port, af, verbose); p = NULL; } @@ -373,7 +575,14 @@ _ftp_connect(char *host, int port, char *user, char *pwd, char *flags) /* did the server request a password? */ if (e == FTP_NEED_PASSWORD) { if (!pwd || !*pwd) - pwd = FTP_ANONYMOUS_PASSWORD; + pwd = getenv("FTP_PASSWORD"); + if (!pwd || !*pwd) { + if ((logname = getlogin()) == 0) + logname = FTP_ANONYMOUS_PASSWORD; + gethostname(localhost, sizeof localhost); + snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); + pwd = pbuf; + } e = _ftp_cmd(cd, "PASS %s", pwd); } @@ -437,8 +646,14 @@ _ftp_cached_connect(struct url *url, char *flags) cd = -1; /* set default port */ - if (!url->port) - url->port = FTP_DEFAULT_PORT; + if (!url->port) { + struct servent *se; + + if ((se = getservbyname("ftp", "tcp")) != NULL) + url->port = ntohs(se->s_port); + else + url->port = FTP_DEFAULT_PORT; + } /* try to use previously cached connection */ if (_ftp_isconnected(url)) { @@ -455,7 +670,7 @@ _ftp_cached_connect(struct url *url, char *flags) if (cached_socket) _ftp_disconnect(cached_socket); cached_socket = cd; - memcpy(&cached_host, url, sizeof(struct url)); + memcpy(&cached_host, url, sizeof *url); } return cd; @@ -474,7 +689,7 @@ fetchGetFTP(struct url *url, char *flags) return NULL; /* initiate the transfer */ - return _ftp_transfer(cd, "RETR", url->doc, "r", flags); + return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); } /* @@ -491,7 +706,7 @@ fetchPutFTP(struct url *url, char *flags) /* initiate the transfer */ return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", - url->doc, "w", flags); + url->doc, "w", url->offset, flags); } /* @@ -505,6 +720,9 @@ fetchStatFTP(struct url *url, struct url_stat *us, char *flags) time_t t; int e, cd; + us->size = -1; + us->atime = us->mtime = 0; + /* connect to server */ if ((cd = _ftp_cached_connect(url, flags)) == NULL) return -1; @@ -534,25 +752,40 @@ fetchStatFTP(struct url *url, struct url_stat *us, char *flags) _ftp_seterr(999); return -1; } + DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) goto ouch; for (ln = last_reply + 4; *ln && isspace(*ln); ln++) /* nothing */ ; - sscanf(ln, "%04d%02d%02d%02d%02d%02d", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, - &tm.tm_hour, &tm.tm_min, &tm.tm_sec); - /* XXX should check the return value from sscanf */ + e = 999; + switch (strspn(ln, "0123456789")) { + case 14: + break; + case 15: + ln++; + ln[0] = '2'; + ln[1] = '0'; + break; + default: + goto ouch; + } + if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) + goto ouch; tm.tm_mon--; tm.tm_year -= 1900; tm.tm_isdst = -1; - t = mktime(&tm); + t = timegm(&tm); if (t == (time_t)-1) t = time(NULL); - else - t += tm.tm_gmtoff; us->mtime = t; us->atime = t; + DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " + "%02d:%02d:%02d\033[m]\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec)); return 0; ouch: diff --git a/lib/libfetch/ftp.errors b/lib/libfetch/ftp.errors index 3dc70281ea11..099de7460fa3 100644 --- a/lib/libfetch/ftp.errors +++ b/lib/libfetch/ftp.errors @@ -18,7 +18,8 @@ 221 OK Service closing control connection 225 OK Data connection open; no transfer in progress 226 OK Requested file action successful -227 OK Entering Passive Mode +227 OK Entering Passive Mode +229 OK Entering Extended Passive Mode 230 OK User logged in, proceed 250 OK Requested file action okay, completed 257 OK File/directory created diff --git a/lib/libfetch/http.c b/lib/libfetch/http.c index 9b32bc214f4a..d28510d27024 100644 --- a/lib/libfetch/http.c +++ b/lib/libfetch/http.c @@ -42,7 +42,7 @@ * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the - * software without specific, written prior permission. M.I.T. makes + * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. @@ -61,13 +61,17 @@ * SUCH DAMAGE. */ #include <sys/param.h> +#include <sys/socket.h> #include <err.h> #include <ctype.h> +#include <locale.h> +#include <netdb.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> #include <unistd.h> #include "fetch.h" @@ -78,6 +82,10 @@ extern char *__progname; #define ENDL "\r\n" +#define HTTP_OK 200 +#define HTTP_PARTIAL 206 +#define HTTP_MOVED 302 + struct cookie { FILE *real_f; @@ -129,6 +137,8 @@ _http_fillbuf(struct cookie *c) } else if (c->encoding == ENC_CHUNKED) { if (c->chunksize == 0) { ln = fgetln(c->real_f, &len); + if (len <= 2) + return NULL; DEBUG(fprintf(stderr, "\033[1m_http_fillbuf(): new chunk: " "%*.*s\033[m\n", (int)len-2, (int)len-2, ln)); sscanf(ln, "%x", &(c->chunksize)); @@ -288,83 +298,183 @@ _http_auth(char *usr, char *pwd) } /* - * Retrieve a file by HTTP + * Connect to server or proxy */ FILE * -fetchGetHTTP(struct url *URL, char *flags) +_http_connect(struct url *URL, char *flags) { - int sd = -1, e, i, enc = ENC_NONE, direct, verbose; - struct cookie *c; - char *ln, *p, *px, *q; - FILE *f, *cf; + int direct, sd = -1, verbose; +#ifdef INET6 + int af = AF_UNSPEC; +#else + int af = AF_INET; +#endif size_t len; - + char *px; + FILE *f; + direct = (flags && strchr(flags, 'd')); verbose = (flags && strchr(flags, 'v')); + if ((flags && strchr(flags, '4'))) + af = AF_INET; + else if ((flags && strchr(flags, '6'))) + af = AF_INET6; - /* allocate cookie */ - if ((c = calloc(1, sizeof(struct cookie))) == NULL) - return NULL; - /* check port */ - if (!URL->port) - URL->port = 80; /* default HTTP port */ + if (!URL->port) { + struct servent *se; + + if (strcasecmp(URL->scheme, "ftp") == 0) + if ((se = getservbyname("ftp", "tcp")) != NULL) + URL->port = ntohs(se->s_port); + else + URL->port = 21; + else + if ((se = getservbyname("http", "tcp")) != NULL) + URL->port = ntohs(se->s_port); + else + URL->port = 80; + } /* attempt to connect to proxy server */ if (!direct && (px = getenv("HTTP_PROXY")) != NULL) { char host[MAXHOSTNAMELEN]; - int port = 3128; /* XXX I think 3128 is default... check? */ + int port = 0; /* measure length */ - len = strcspn(px, ":"); +#ifdef INET6 + if (px[0] != '[' || + (len = strcspn(px, "]")) >= strlen(px) || + (px[++len] != '\0' && px[len] != ':')) +#endif + len = strcspn(px, ":"); /* get port (XXX atoi is a little too tolerant perhaps?) */ - if (px[len] == ':') + if (px[len] == ':') { + if (strspn(px+len+1, "0123456789") != strlen(px+len+1) + || strlen(px+len+1) > 5) { + /* XXX we should emit some kind of warning */ + } port = atoi(px+len+1); + if (port < 1 || port > 65535) { + /* XXX we should emit some kind of warning */ + } + } + if (!port) { +#if 0 + /* + * commented out, since there is currently no service name + * for HTTP proxies + */ + struct servent *se; + + if ((se = getservbyname("xxxx", "tcp")) != NULL) + port = ntohs(se->s_port); + else +#endif + port = 3128; + } /* get host name */ +#ifdef INET6 + if (len > 1 && px[0] == '[' && px[len - 1] == ']') { + px++; + len -= 2; + } +#endif if (len >= MAXHOSTNAMELEN) len = MAXHOSTNAMELEN - 1; strncpy(host, px, len); host[len] = 0; /* connect */ - sd = _fetch_connect(host, port, verbose); + sd = _fetch_connect(host, port, af, verbose); } /* if no proxy is configured or could be contacted, try direct */ if (sd == -1) { - if ((sd = _fetch_connect(URL->host, URL->port, verbose)) == -1) + if (strcasecmp(URL->scheme, "ftp") == 0) + goto ouch; + if ((sd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1) goto ouch; } /* reopen as stream */ if ((f = fdopen(sd, "r+")) == NULL) goto ouch; - c->real_f = f; + + return f; +ouch: + if (sd >= 0) + close(sd); + _http_seterr(999); /* XXX do this properly RSN */ + return NULL; +} + +/* + * Check a header line + */ +char * +_http_match(char *str, char *hdr) +{ + while (*str && *hdr && tolower(*str++) == tolower(*hdr++)) + /* nothing */; + if (*str || *hdr != ':') + return NULL; + while (*hdr && isspace(*++hdr)) + /* nothing */; + return hdr; +} + +/* + * Send a HEAD or GET request + */ +int +_http_request(FILE *f, char *op, struct url *URL, char *flags) +{ + int e, verbose; + char *ln, *p; + size_t len; + char *host; +#ifdef INET6 + char hbuf[MAXHOSTNAMELEN + 1]; +#endif + + verbose = (flags && strchr(flags, 'v')); + + host = URL->host; +#ifdef INET6 + if (strchr(URL->host, ':')) { + snprintf(hbuf, sizeof(hbuf), "[%s]", URL->host); + host = hbuf; + } +#endif + /* send request (proxies require absolute form, so use that) */ if (verbose) - _fetch_info("requesting http://%s:%d%s", - URL->host, URL->port, URL->doc); - _http_cmd(f, "GET http://%s:%d%s HTTP/1.1" ENDL, - URL->host, URL->port, URL->doc); + _fetch_info("requesting %s://%s:%d%s", + URL->scheme, host, URL->port, URL->doc); + _http_cmd(f, "%s %s://%s:%d%s HTTP/1.1" ENDL, + op, URL->scheme, host, URL->port, URL->doc); /* start sending headers away */ if (URL->user[0] || URL->pwd[0]) { char *auth_str = _http_auth(URL->user, URL->pwd); if (!auth_str) - goto fouch; + return 999; /* XXX wrong */ _http_cmd(f, "Authorization: Basic %s" ENDL, auth_str); free(auth_str); } - _http_cmd(f, "Host: %s:%d" ENDL, URL->host, URL->port); + _http_cmd(f, "Host: %s:%d" ENDL, host, URL->port); _http_cmd(f, "User-Agent: %s " _LIBFETCH_VER ENDL, __progname); + if (URL->offset) + _http_cmd(f, "Range: bytes=%lld-" ENDL, URL->offset); _http_cmd(f, "Connection: close" ENDL ENDL); /* get response */ if ((ln = fgetln(f, &len)) == NULL) - goto fouch; + return 999; DEBUG(fprintf(stderr, "response: [\033[1m%*.*s\033[m]\n", (int)len-2, (int)len-2, ln)); @@ -375,14 +485,46 @@ fetchGetHTTP(struct url *URL, char *flags) while ((p < ln + len) && !isdigit(*p)) p++; if (!isdigit(*p)) - goto fouch; + return 999; + e = atoi(p); DEBUG(fprintf(stderr, "code: [\033[1m%d\033[m]\n", e)); + return e; +} + +/* + * Retrieve a file by HTTP + */ +FILE * +fetchGetHTTP(struct url *URL, char *flags) +{ + int e, enc = ENC_NONE, i, noredirect; + struct cookie *c; + char *ln, *p, *q; + FILE *f, *cf; + size_t len; + off_t pos = 0; + + noredirect = (flags && strchr(flags, 'A')); - /* add code to handle redirects later */ - if (e != 200) { + /* allocate cookie */ + if ((c = calloc(1, sizeof *c)) == NULL) + return NULL; + + /* connect */ + if ((f = _http_connect(URL, flags)) == NULL) { + free(c); + return NULL; + } + c->real_f = f; + + e = _http_request(f, "GET", URL, flags); + if (e != (URL->offset ? HTTP_PARTIAL : HTTP_OK) + && (e != HTTP_MOVED || noredirect)) { _http_seterr(e); - goto fouch; + free(c); + fclose(f); + return NULL; } /* browse through header */ @@ -391,32 +533,48 @@ fetchGetHTTP(struct url *URL, char *flags) goto fouch; if ((ln[0] == '\r') || (ln[0] == '\n')) break; - DEBUG(fprintf(stderr, "header: [\033[1m%*.*s\033[m]\n", - (int)len-2, (int)len-2, ln)); -#define XFERENC "Transfer-Encoding:" - if (strncasecmp(ln, XFERENC, sizeof(XFERENC)-1) == 0) { - p = ln + sizeof(XFERENC) - 1; - while ((p < ln + len) && isspace(*p)) - p++; - for (q = p; (q < ln + len) && !isspace(*q); q++) + while (isspace(ln[len-1])) + --len; + ln[len] = '\0'; /* XXX */ + DEBUG(fprintf(stderr, "header: [\033[1m%s\033[m]\n", ln)); + if ((p = _http_match("Location", ln)) != NULL) { + struct url *url; + + for (q = p; *q && !isspace(*q); q++) + /* VOID */ ; + *q = 0; + if ((url = fetchParseURL(p)) == NULL) + goto fouch; + url->offset = URL->offset; + url->length = URL->length; + DEBUG(fprintf(stderr, "location: [\033[1m%s\033[m]\n", p)); + cf = fetchGetHTTP(url, flags); + fetchFreeURL(url); + fclose(f); + return cf; + } else if ((p = _http_match("Transfer-Encoding", ln)) != NULL) { + for (q = p; *q && !isspace(*q); q++) /* VOID */ ; *q = 0; if (strcasecmp(p, "chunked") == 0) enc = ENC_CHUNKED; - DEBUG(fprintf(stderr, "xferenc: [\033[1m%s\033[m]\n", p)); -#undef XFERENC -#define CONTTYPE "Content-Type:" - } else if (strncasecmp(ln, CONTTYPE, sizeof(CONTTYPE)-1) == 0) { - p = ln + sizeof(CONTTYPE) - 1; - while ((p < ln + len) && isspace(*p)) - p++; - for (i = 0; p < ln + len; p++) - if (i < HTTPCTYPELEN) - c->content_type[i++] = *p; + DEBUG(fprintf(stderr, "transfer encoding: [\033[1m%s\033[m]\n", p)); + } else if ((p = _http_match("Content-Type", ln)) != NULL) { + for (i = 0; *p && i < HTTPCTYPELEN; p++, i++) + c->content_type[i] = *p; do c->content_type[i--] = 0; while (isspace(c->content_type[i])); - DEBUG(fprintf(stderr, "conttype: [\033[1m%s\033[m]\n", + DEBUG(fprintf(stderr, "content type: [\033[1m%s\033[m]\n", c->content_type)); -#undef CONTTYPE + } else if ((p = _http_match("Content-Range", ln)) != NULL) { + if (strncasecmp(p, "bytes ", 6) != 0) + goto fouch; + p += 6; + while (*p && isdigit(*p)) + pos = pos * 10 + (*p++ - '0'); + /* XXX wouldn't hurt to be slightly more paranoid here */ + DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", pos)); + if (pos > URL->offset) + goto fouch; } } @@ -429,19 +587,22 @@ fetchGetHTTP(struct url *URL, char *flags) (int (*)(void *))_http_closefn); if (cf == NULL) goto fouch; + + while (pos < URL->offset) + if (fgetc(cf) == EOF) + goto cfouch; + return cf; -ouch: - if (sd >= 0) - close(sd); - free(c); - _http_seterr(999); /* XXX do this properly RSN */ - return NULL; fouch: fclose(f); free(c); _http_seterr(999); /* XXX do this properly RSN */ return NULL; +cfouch: + fclose(cf); + _http_seterr(999); /* XXX do this properly RSN */ + return NULL; } FILE * @@ -455,10 +616,82 @@ fetchPutHTTP(struct url *URL, char *flags) * Get an HTTP document's metadata */ int -fetchStatHTTP(struct url *url, struct url_stat *us, char *flags) +fetchStatHTTP(struct url *URL, struct url_stat *us, char *flags) { - warnx("fetchStatHTTP(): not implemented"); - return -1; + int e, noredirect; + size_t len; + char *ln, *p, *q; + FILE *f; + + noredirect = (flags && strchr(flags, 'A')); + + us->size = -1; + us->atime = us->mtime = 0; + + /* connect */ + if ((f = _http_connect(URL, flags)) == NULL) + return -1; + + e = _http_request(f, "HEAD", URL, flags); + if (e != HTTP_OK && (e != HTTP_MOVED || noredirect)) { + _http_seterr(e); + fclose(f); + return -1; + } + + while (1) { + if ((ln = fgetln(f, &len)) == NULL) + goto fouch; + if ((ln[0] == '\r') || (ln[0] == '\n')) + break; + while (isspace(ln[len-1])) + --len; + ln[len] = '\0'; /* XXX */ + DEBUG(fprintf(stderr, "header: [\033[1m%s\033[m]\n", ln)); + if ((p = _http_match("Location", ln)) != NULL) { + struct url *url; + + for (q = p; *q && !isspace(*q); q++) + /* VOID */ ; + *q = 0; + if ((url = fetchParseURL(p)) == NULL) + goto ouch; + url->offset = URL->offset; + url->length = URL->length; + DEBUG(fprintf(stderr, "location: [\033[1m%s\033[m]\n", p)); + e = fetchStatHTTP(url, us, flags); + fetchFreeURL(url); + fclose(f); + return e; + } else if ((p = _http_match("Last-Modified", ln)) != NULL) { + struct tm tm; + char locale[64]; + + strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale); + setlocale(LC_TIME, "C"); + strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm); + /* XXX should add support for date-2 and date-3 */ + setlocale(LC_TIME, locale); + us->atime = us->mtime = timegm(&tm); + DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " + "%02d:%02d:%02d\033[m]\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec)); + } else if ((p = _http_match("Content-Length", ln)) != NULL) { + us->size = 0; + while (*p && isdigit(*p)) + us->size = us->size * 10 + (*p++ - '0'); + DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", us->size)); + } + } + + fclose(f); + return 0; + ouch: + _http_seterr(999); /* XXX do this properly RSN */ + fouch: + fclose(f); + return -1; } /* |
