Add newborns.
authorjbj <devnull@localhost>
Sat, 3 Jul 1999 23:36:35 +0000 (23:36 +0000)
committerjbj <devnull@localhost>
Sat, 3 Jul 1999 23:36:35 +0000 (23:36 +0000)
CVS patchset: 3126
CVS date: 1999/07/03 23:36:35

lib/ftp.c [new file with mode: 0644]
lib/rpmurl.h [new file with mode: 0644]
lib/url.c [new file with mode: 0644]

diff --git a/lib/ftp.c b/lib/ftp.c
new file mode 100644 (file)
index 0000000..9b45fed
--- /dev/null
+++ b/lib/ftp.c
@@ -0,0 +1,690 @@
+#include "system.h"
+
+#if !defined(HAVE_CONFIG_H)
+#define HAVE_MACHINE_TYPES_H 1
+#define HAVE_ALLOCA_H 1
+#define HAVE_NETINET_IN_SYSTM_H 1
+#define HAVE_SYS_SOCKET_H 1
+#endif
+
+#ifndef __LCLINT__
+#if HAVE_MACHINE_TYPES_H
+# include <machine/types.h>
+#endif
+#endif
+
+#if HAVE_NETINET_IN_SYSTM_H
+# include <sys/types.h>
+# include <netinet/in_systm.h>
+#endif
+
+#if ! HAVE_HERRNO
+extern int h_errno;
+#endif
+
+#include <stdarg.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <arpa/telnet.h>
+
+#include "rpmlib.h"
+#include "rpmio.h"
+
+#if !defined(HAVE_INET_ATON)
+int inet_aton(const char *cp, struct in_addr *inp);
+#endif
+
+#define TIMEOUT_SECS 60
+#define BUFFER_SIZE 4096
+
+#if defined(USE_ALT_DNS) && USE_ALT_DNS 
+#include "dns.h"
+#endif
+
+#include "rpmurl.h"
+
+#ifdef __MINT__
+# ifndef EAGAIN
+#  define EAGAIN EWOULDBLOCK
+# endif
+# ifndef O_NONBLOCK
+#  define O_NONBLOCK O_NDELAY
+# endif
+#endif
+
+static int ftpDebug = 0;
+static int ftpTimeoutSecs = TIMEOUT_SECS;
+static int httpTimeoutSecs = TIMEOUT_SECS;
+
+static rpmCallbackFunction     urlNotify = NULL;
+static void *                  urlNotifyData = NULL;
+static int                     urlNotifyCount = -1;
+
+void urlSetCallback(rpmCallbackFunction notify, void *notifyData, int notifyCount) {
+    urlNotify = notify;
+    urlNotifyData = notifyData;
+    urlNotifyCount = (notifyCount >= 0) ? notifyCount : 4096;
+}
+
+static int checkResponse(int fd, int secs, int *ecp, char ** str) {
+    static char buf[BUFFER_SIZE + 1];
+    int bufLength = 0; 
+    fd_set emptySet, readSet;
+    char *se, *s;
+    struct timeval timeout;
+    int bytesRead, rc = 0;
+    int doesContinue = 1;
+    char errorCode[4];
+    errorCode[0] = '\0';
+    
+    do {
+       /*
+        * XXX In order to preserve both getFile and getFd methods with
+        * XXX HTTP, the response is read 1 char at a time with breaks on
+        * XXX newlines.
+        */
+       do {
+           FD_ZERO(&emptySet);
+           FD_ZERO(&readSet);
+           FD_SET(fd, &readSet);
+
+           timeout.tv_sec = secs;
+           timeout.tv_usec = 0;
+    
+           rc = select(fd + 1, &readSet, &emptySet, &emptySet, &timeout);
+           if (rc < 1) {
+               if (rc == 0) 
+                   return FTPERR_BAD_SERVER_RESPONSE;
+               else
+                   rc = FTPERR_UNKNOWN;
+           } else
+               rc = 0;
+
+           s = buf + bufLength;
+           bytesRead = read(fd, s, 1);
+           bufLength += bytesRead;
+           buf[bufLength] = '\0';
+       } while (bufLength < sizeof(buf) && *s != '\n');
+
+       /*
+        * Divide the response into lines. Skip continuation lines.
+        */
+       s = se = buf;
+       while (*se != '\0') {
+               while (*se && *se != '\n') se++;
+
+               if (se > s && se[-1] == '\r')
+                  se[-1] = '\0';
+               if (*se == '\0')
+                       break;
+
+               /* HTTP header termination on empty line */
+               if (*s == '\0') {
+                       doesContinue = 0;
+                       break;
+               }
+               *se++ = '\0';
+
+               /* HTTP: look for "HTTP/1.1 123 ..." */
+               if (!strncmp(s, "HTTP", 4)) {
+                       char *e;
+                       if ((e = strchr(s, ' ')) != NULL) {
+                           e++;
+                           if (strchr("0123456789", *e))
+                               strncpy(errorCode, e, 3);
+                           errorCode[3] = '\0';
+                       }
+                       s = se;
+                       continue;
+               }
+
+               /* FTP: look for "123-" and/or "123 " */
+               if (strchr("0123456789", *s)) {
+                       if (errorCode[0]) {
+                           if (!strncmp(s, errorCode, 3) && s[3] == ' ')
+                               doesContinue = 0;
+                       } else {
+                           strncpy(errorCode, s, 3);
+                           errorCode[3] = '\0';
+                           if (s[3] != '-') {
+                               doesContinue = 0;
+                           } 
+                       }
+               }
+               s = se;
+       }
+
+       if (doesContinue && se > s) {
+           bufLength = se - s - 1;
+           if (s != buf)
+               memcpy(buf, s, bufLength);
+       } else {
+           bufLength = 0;
+       }
+    } while (doesContinue && !rc);
+
+if (ftpDebug)
+fprintf(stderr, "<- %s\n", buf);
+
+    if (str)   *str = buf;
+    if (ecp)   *ecp = atoi(errorCode);
+
+    return rc;
+}
+
+static int ftpCheckResponse(urlinfo *u, char ** str) {
+    int ec = 0;
+    int rc =  checkResponse(u->ftpControl, ftpTimeoutSecs, &ec, str);
+
+    switch (ec) {
+    case 550:
+       return FTPERR_FILE_NOT_FOUND;
+       break;
+    case 552:
+       return FTPERR_NIC_ABORT_IN_PROGRESS;
+       break;
+    default:
+       if (ec >= 400 && ec <= 599)
+           return FTPERR_BAD_SERVER_RESPONSE;
+       break;
+    }
+    return rc;
+}
+
+static int ftpCommand(urlinfo *u, char * command, ...) {
+    va_list ap;
+    int len;
+    char * s;
+    char * buf;
+
+    va_start(ap, command);
+    len = strlen(command) + 2;
+    s = va_arg(ap, char *);
+    while (s) {
+       len += strlen(s) + 1;
+       s = va_arg(ap, char *);
+    }
+    va_end(ap);
+
+    buf = alloca(len + 1);
+
+    va_start(ap, command);
+    strcpy(buf, command);
+    strcat(buf, " ");
+    s = va_arg(ap, char *);
+    while (s) {
+       strcat(buf, s);
+       strcat(buf, " ");
+       s = va_arg(ap, char *);
+    }
+    va_end(ap);
+
+    buf[len - 2] = '\r';
+    buf[len - 1] = '\n';
+    buf[len] = '\0';
+
+if (ftpDebug)
+fprintf(stderr, "-> %s", buf);
+    if (write(u->ftpControl, buf, len) != len) {
+       return FTPERR_SERVER_IO_ERROR;
+    }
+
+    return ftpCheckResponse(u, NULL);
+}
+
+#if !defined(USE_ALT_DNS) || !USE_ALT_DNS 
+static int mygethostbyname(const char * host, struct in_addr * address) {
+    struct hostent * hostinfo;
+
+    hostinfo = gethostbyname(host);
+    if (!hostinfo) return 1;
+
+    memcpy(address, hostinfo->h_addr_list[0], hostinfo->h_length);
+    return 0;
+}
+#endif
+
+static int getHostAddress(const char * host, struct in_addr * address) {
+    if (isdigit(host[0])) {
+      if (!inet_aton(host, address)) {
+         return FTPERR_BAD_HOST_ADDR;
+      }
+    } else {
+      if (mygethostbyname(host, address)) {
+         errno = h_errno;
+         return FTPERR_BAD_HOSTNAME;
+      }
+    }
+    
+    return 0;
+}
+
+static int tcpConnect(const char *host, int port)
+{
+    struct sockaddr_in sin;
+    int sock = -1;
+    int rc;
+
+    sin.sin_family = AF_INET;
+    sin.sin_port = htons(port);
+    sin.sin_addr.s_addr = INADDR_ANY;
+    
+  do {
+    if ((rc = getHostAddress(host, &sin.sin_addr)) < 0)
+       break;
+
+    if ((sock = socket(sin.sin_family, SOCK_STREAM, IPPROTO_IP)) < 0) {
+       rc = FTPERR_FAILED_CONNECT;
+       break;
+    }
+
+    if (connect(sock, (struct sockaddr *) &sin, sizeof(sin))) {
+       rc = FTPERR_FAILED_CONNECT;
+       break;
+    }
+  } while (0);
+
+    if (rc < 0 && sock >= 0) {
+       close(sock);
+       return rc;
+    }
+
+if (ftpDebug)
+fprintf(stderr,"++ connect %s:%d on fd %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), sock);
+
+    return sock;
+}
+
+int httpOpen(urlinfo *u)
+{
+    int sock;
+    const char *host;
+    const char *path;
+    int port;
+    char *buf;
+    size_t len;
+
+    if (u == NULL || ((host = (u->proxyh ? u->proxyh : u->host)) == NULL))
+       return FTPERR_BAD_HOSTNAME;
+
+    if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = 80;
+
+    path = (u->proxyh || u->proxyp > 0) ? u->url : u->path;
+    if ((sock = tcpConnect(host, port)) < 0)
+       return sock;
+
+    len = strlen(path) + sizeof("GET  HTTP/1.0\r\n\r\n");
+    buf = alloca(len);
+    strcpy(buf, "GET ");
+    strcat(buf, path);
+    strcat(buf, " HTTP/1.0\r\n");
+    strcat(buf, "\r\n");
+
+    if (write(sock, buf, len) != len) {
+       close(sock);
+       return FTPERR_SERVER_IO_ERROR;
+    }
+
+if (ftpDebug)
+fprintf(stderr, "-> %s", buf);
+
+  { int ec = 0;
+    int rc;
+    rc = checkResponse(sock, httpTimeoutSecs, &ec, NULL);
+
+    switch (ec) {
+    default:
+       if (rc == 0 && ec != 200)       /* not HTTP_OK */
+           rc = FTPERR_FILE_NOT_FOUND;
+       break;
+    }
+
+    if (rc < 0) {
+       close(sock);
+       return rc;
+    }
+  }
+
+    return sock;
+}
+
+int ftpOpen(urlinfo *u)
+{
+    const char * host;
+    const char * user;
+    const char * password;
+    int port;
+    int rc;
+
+    if (u == NULL || ((host = (u->proxyh ? u->proxyh : u->host)) == NULL))
+       return FTPERR_BAD_HOSTNAME;
+
+    if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = IPPORT_FTP;
+
+    if ((user = (u->proxyu ? u->proxyu : u->user)) == NULL)
+       user = "anonymous";
+
+    if ((password = u->password) == NULL) {
+       if (getuid()) {
+           struct passwd * pw = getpwuid(getuid());
+           char *myp = alloca(strlen(pw->pw_name) + sizeof("@"));
+           strcpy(myp, pw->pw_name);
+           strcat(myp, "@");
+           password = myp;
+       } else {
+           password = "root@";
+       }
+    }
+
+    if ((u->ftpControl = tcpConnect(host, port)) < 0)
+       return u->ftpControl;
+
+    /* ftpCheckResponse() assumes the socket is nonblocking */
+    if (fcntl(u->ftpControl, F_SETFL, O_NONBLOCK)) {
+       rc = FTPERR_FAILED_CONNECT;
+       goto errxit;
+    }
+
+    if ((rc = ftpCheckResponse(u, NULL))) {
+       return rc;     
+    }
+
+    if ((rc = ftpCommand(u, "USER", user, NULL)))
+       goto errxit;
+
+    if ((rc = ftpCommand(u, "PASS", password, NULL)))
+       goto errxit;
+
+    if ((rc = ftpCommand(u, "TYPE", "I", NULL)))
+       goto errxit;
+
+    return u->ftpControl;
+
+errxit:
+    close(u->ftpControl);
+    u->ftpControl = -1;
+    return rc;
+}
+
+static int copyData(FD_t sfd, FD_t tfd) {
+    char buf[BUFFER_SIZE];
+    fd_set emptySet, readSet;
+    struct timeval timeout;
+    int bytesRead;
+    int bytesCopied = 0;
+    int rc;
+    int notifier = -1;
+
+    if (urlNotify) {
+       (*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
+               0, 0, NULL, urlNotifyData);
+    }
+    
+    while (1) {
+       FD_ZERO(&emptySet);
+       FD_ZERO(&readSet);
+       FD_SET(fdFileno(sfd), &readSet);
+
+       timeout.tv_sec = ftpTimeoutSecs;
+       timeout.tv_usec = 0;
+    
+       rc = select(fdFileno(sfd) + 1, &readSet, &emptySet, &emptySet, &timeout);
+       if (rc == 0) {
+           rc = FTPERR_SERVER_TIMEOUT;
+           break;
+       } else if (rc < 0) {
+           rc = FTPERR_UNKNOWN;
+           break;
+       }
+
+       bytesRead = fdRead(sfd, buf, sizeof(buf));
+       if (bytesRead == 0) {
+           rc = 0;
+           break;
+       }
+
+       if (fdWrite(tfd, buf, bytesRead) != bytesRead) {
+           rc = FTPERR_FILE_IO_ERROR;
+           break;
+       }
+       bytesCopied += bytesRead;
+       if (urlNotify && urlNotifyCount > 0) {
+           int n = bytesCopied/urlNotifyCount;
+           if (n != notifier) {
+               (*urlNotify) (NULL, RPMCALLBACK_INST_PROGRESS,
+                       bytesCopied, 0, NULL, urlNotifyData);
+               notifier = n;
+           }
+       }
+    }
+
+if (ftpDebug)
+fprintf(stderr, "++ copied %d bytes: %s\n", bytesCopied, ftpStrerror(rc));
+
+    if (urlNotify) {
+       (*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
+               bytesCopied, bytesCopied, NULL, urlNotifyData);
+    }
+    
+    fdClose(sfd);
+    return rc;
+}
+
+int ftpAbort(FD_t fd) {
+    urlinfo *u = (urlinfo *)fd->fd_url;
+    char buf[BUFFER_SIZE];
+    int rc;
+    int tosecs = ftpTimeoutSecs;
+
+if (ftpDebug)
+fprintf(stderr, "-> ABOR\n");
+
+    sprintf(buf, "%c%c%c", IAC, IP, IAC);
+    send(u->ftpControl, buf, 3, MSG_OOB);
+    sprintf(buf, "%cABOR\r\n", DM);
+    if (write(u->ftpControl, buf, 7) != 7) {
+       close(u->ftpControl);
+       u->ftpControl = -1;
+       return FTPERR_SERVER_IO_ERROR;
+    }
+    if (fdFileno(fd) >= 0) {
+       while(read(fdFileno(fd), buf, sizeof(buf)) > 0)
+           ;
+    }
+
+    ftpTimeoutSecs = 10;
+    if ((rc = ftpCheckResponse(u, NULL)) == FTPERR_NIC_ABORT_IN_PROGRESS) {
+       rc = ftpCheckResponse(u, NULL);
+    }
+    rc = ftpCheckResponse(u, NULL);
+    ftpTimeoutSecs = tosecs;
+
+    if (fdFileno(fd) >= 0)
+       fdClose(fd);
+    return 0;
+}
+
+static int ftpGetFileDone(urlinfo *u) {
+    if (u->ftpGetFileDoneNeeded) {
+       u->ftpGetFileDoneNeeded = 0;
+       if (ftpCheckResponse(u, NULL))
+           return FTPERR_BAD_SERVER_RESPONSE;
+    }
+    return 0;
+}
+
+int ftpGetFileDesc(FD_t fd)
+{
+    urlinfo *u;
+    const char *remotename;
+    struct sockaddr_in dataAddress;
+    int i, j;
+    char * passReply;
+    char * chptr;
+    char * retrCommand;
+    int rc;
+
+    u = (urlinfo *)fd->fd_url;
+    remotename = u->path;
+
+/*
+ * XXX When ftpGetFileDesc() is called, there may be a lurking
+ * XXX transfer complete message (if ftpGetFileDone() was not
+ * XXX called to clear that message). Clear that message now.
+ */
+
+    if (u->ftpGetFileDoneNeeded)
+       rc = ftpGetFileDone(u);
+
+if (ftpDebug)
+fprintf(stderr, "-> PASV\n");
+    if (write(u->ftpControl, "PASV\r\n", 6) != 6)
+       return FTPERR_SERVER_IO_ERROR;
+
+    if ((rc = ftpCheckResponse(u, &passReply)))
+       return FTPERR_PASSIVE_ERROR;
+
+    chptr = passReply;
+    while (*chptr && *chptr != '(') chptr++;
+    if (*chptr != '(') return FTPERR_PASSIVE_ERROR; 
+    chptr++;
+    passReply = chptr;
+    while (*chptr && *chptr != ')') chptr++;
+    if (*chptr != ')') return FTPERR_PASSIVE_ERROR;
+    *chptr-- = '\0';
+
+    while (*chptr && *chptr != ',') chptr--;
+    if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
+    chptr--;
+    while (*chptr && *chptr != ',') chptr--;
+    if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
+    *chptr++ = '\0';
+    
+    /* now passReply points to the IP portion, and chptr points to the
+       port number portion */
+
+    dataAddress.sin_family = AF_INET;
+    if (sscanf(chptr, "%d,%d", &i, &j) != 2) {
+       return FTPERR_PASSIVE_ERROR;
+    }
+    dataAddress.sin_port = htons((i << 8) + j);
+
+    chptr = passReply;
+    while (*chptr++) {
+       if (*chptr == ',') *chptr = '.';
+    }
+
+    if (!inet_aton(passReply, &dataAddress.sin_addr)) 
+       return FTPERR_PASSIVE_ERROR;
+
+    fd->fd_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
+    if (fdFileno(fd) < 0) {
+       return FTPERR_FAILED_CONNECT;
+    }
+
+    retrCommand = alloca(strlen(remotename) + 20);
+    sprintf(retrCommand, "RETR %s\r\n", remotename);
+    i = strlen(retrCommand);
+   
+    while (connect(fdFileno(fd), (struct sockaddr *) &dataAddress, 
+               sizeof(dataAddress)) < 0) {
+       if (errno == EINTR)
+           continue;
+       fdClose(fd);
+       return FTPERR_FAILED_DATA_CONNECT;
+    }
+
+if (ftpDebug)
+fprintf(stderr, "-> %s", retrCommand);
+    if (write(u->ftpControl, retrCommand, i) != i) {
+       return FTPERR_SERVER_IO_ERROR;
+    }
+
+    if ((rc = ftpCheckResponse(u, NULL))) {
+       fdClose(fd);
+       return rc;
+    }
+
+    u->ftpGetFileDoneNeeded = 1;
+    return 0;
+}
+
+int httpGetFile(FD_t sfd, FD_t tfd) {
+    return copyData(sfd, tfd);
+}
+
+int ftpGetFile(FD_t sfd, FD_t tfd)
+{
+    urlinfo *u;
+    int rc;
+
+    /* XXX sfd will be freed by copyData -- grab sfd->fd_url now */
+    u = (urlinfo *)sfd->fd_url;
+
+    /* XXX normally sfd = ufdOpen(...) and this code does not execute */
+    if (fdFileno(sfd) < 0 && (rc = ftpGetFileDesc(sfd)) < 0) {
+       fdClose(sfd);
+       return rc;
+    }
+
+    rc = copyData(sfd, tfd);
+    if (rc < 0)
+       return rc;
+
+    return ftpGetFileDone(u);
+}
+
+int ftpClose(FD_t fd) {
+    int fdno = ((urlinfo *)fd->fd_url)->ftpControl;
+    if (fdno >= 0)
+       close(fdno);
+    return 0;
+}
+
+const char *ftpStrerror(int errorNumber) {
+  switch (errorNumber) {
+    case 0:
+       return _("Success");
+
+    case FTPERR_BAD_SERVER_RESPONSE:
+       return _("Bad server response");
+
+    case FTPERR_SERVER_IO_ERROR:
+       return _("Server IO error");
+
+    case FTPERR_SERVER_TIMEOUT:
+       return _("Server timeout");
+
+    case FTPERR_BAD_HOST_ADDR:
+       return _("Unable to lookup server host address");
+
+    case FTPERR_BAD_HOSTNAME:
+       return _("Unable to lookup server host name");
+
+    case FTPERR_FAILED_CONNECT:
+       return _("Failed to connect to server");
+
+    case FTPERR_FAILED_DATA_CONNECT:
+       return _("Failed to establish data connection to server");
+
+    case FTPERR_FILE_IO_ERROR:
+       return _("IO error to local file");
+
+    case FTPERR_PASSIVE_ERROR:
+       return _("Error setting remote server to passive mode");
+
+    case FTPERR_FILE_NOT_FOUND:
+       return _("File not found on server");
+
+    case FTPERR_NIC_ABORT_IN_PROGRESS:
+       return _("Abort in progress");
+
+    case FTPERR_UNKNOWN:
+    default:
+       return _("Unknown or unexpected error");
+  }
+}
+
diff --git a/lib/rpmurl.h b/lib/rpmurl.h
new file mode 100644 (file)
index 0000000..879b594
--- /dev/null
@@ -0,0 +1,81 @@
+#ifndef H_RPMURL
+#define H_RPMURL
+
+#ifndef IPPORT_FTP
+#define IPPORT_FTP     21
+#endif
+#ifndef        IPPORT_HTTP
+#define        IPPORT_HTTP     80
+#endif
+
+#define FTPERR_BAD_SERVER_RESPONSE   -1
+#define FTPERR_SERVER_IO_ERROR       -2
+#define FTPERR_SERVER_TIMEOUT        -3
+#define FTPERR_BAD_HOST_ADDR         -4
+#define FTPERR_BAD_HOSTNAME          -5
+#define FTPERR_FAILED_CONNECT        -6
+#define FTPERR_FILE_IO_ERROR         -7
+#define FTPERR_PASSIVE_ERROR         -8
+#define FTPERR_FAILED_DATA_CONNECT   -9
+#define FTPERR_FILE_NOT_FOUND        -10
+#define FTPERR_NIC_ABORT_IN_PROGRESS -11
+#define FTPERR_UNKNOWN               -100
+
+typedef enum {
+    URL_IS_UNKNOWN     = 0,
+    URL_IS_DASH                = 1,
+    URL_IS_PATH                = 2,
+    URL_IS_FTP         = 3,
+    URL_IS_HTTP                = 4
+} urltype;
+
+typedef struct urlinfo {
+    const char *url;           /* copy of original url */
+    const char *service;
+    const char *user;
+    const char *password;
+    const char *host;
+    const char *portstr;
+    const char *path;
+    const char *proxyu;                /* FTP: proxy user */
+    const char *proxyh;                /* FTP/HTTP: proxy host */
+    int proxyp;                        /* FTP/HTTP: proxy port */
+    int        port;
+    int ftpControl;
+    int ftpGetFileDoneNeeded;
+    int openError;             /* Type of open failure */
+} urlinfo;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char * ftpStrerror(int ftpErrno);
+
+void   urlSetCallback(rpmCallbackFunction notify, void *notifyData, int notifyCount);
+int    httpOpen(urlinfo *u);
+int    ftpOpen(urlinfo *u);
+
+int    httpGetFile(FD_t sfd, FD_t tfd);
+int    ftpGetFile(FD_t sfd, FD_t tfd);
+int    ftpGetFileDesc(FD_t);
+int    ftpAbort(FD_t fd);
+int    ftpClose(FD_t fd);
+
+urltype        urlIsURL(const char * url);
+int    urlSplit(const char *url, urlinfo **u);
+urlinfo        *newUrlinfo(void);
+void   freeUrlinfo(urlinfo *u);
+
+FD_t   ufdOpen(const char * pathname, int flags, mode_t mode);
+int    ufdClose(FD_t fd);
+const char *urlStrerror(const char *url);
+
+int    urlGetFile(const char * url, const char * dest);
+void    urlInvalidateCache(const char * url);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMURL */
diff --git a/lib/url.c b/lib/url.c
new file mode 100644 (file)
index 0000000..42f074d
--- /dev/null
+++ b/lib/url.c
@@ -0,0 +1,477 @@
+#include "system.h"
+
+#include <netinet/in.h>
+
+#include "build/rpmbuild.h"
+
+#include "rpmurl.h"
+
+static struct urlstring {
+    const char *leadin;
+    urltype    ret;
+} urlstrings[] = {
+    { "file://",       URL_IS_PATH },
+    { "ftp://",                URL_IS_FTP },
+    { "http://",       URL_IS_HTTP },
+    { "-",             URL_IS_DASH },
+    { NULL,            URL_IS_UNKNOWN }
+};
+
+void freeUrlinfo(urlinfo *u)
+{
+    if (u->ftpControl >= 0)
+       close(u->ftpControl);
+    FREE(u->url);
+    FREE(u->service);
+    FREE(u->user);
+    FREE(u->password);
+    FREE(u->host);
+    FREE(u->portstr);
+    FREE(u->path);
+    FREE(u->proxyu);
+    FREE(u->proxyh);
+
+    FREE(u);
+}
+
+urlinfo *newUrlinfo(void)
+{
+    urlinfo *u;
+    if ((u = malloc(sizeof(*u))) == NULL)
+       return NULL;
+    memset(u, 0, sizeof(*u));
+    u->proxyp = -1;
+    u->port = -1;
+    u->ftpControl = -1;
+    u->ftpGetFileDoneNeeded = 0;
+    return u;
+}
+
+static int urlStrcmp(const char *str1, const char *str2)
+{
+    if (str1 && str2) {
+       return (strcmp(str1, str2));
+    } else
+       if (str1 != str2)
+           return -1;
+    return 0;
+}
+
+static void findUrlinfo(urlinfo **uret, int mustAsk)
+{
+    urlinfo *u;
+    urlinfo **empty;
+    static urlinfo **uCache = NULL;
+    static int uCount = 0;
+    int i;
+
+    if (uret == NULL)
+       return;
+
+    u = *uret;
+
+    empty = NULL;
+    for (i = 0; i < uCount; i++) {
+       urlinfo *ou;
+       if ((ou = uCache[i]) == NULL) {
+           if (empty == NULL)
+               empty = &uCache[i];
+           continue;
+       }
+       /* Check for cache-miss condition. A cache miss is
+        *    a) both items are not NULL and don't compare.
+        *    b) either of the items is not NULL.
+        */
+       if (urlStrcmp(u->service, ou->service))
+           continue;
+       if (urlStrcmp(u->service, ou->service))
+           continue;
+       if (urlStrcmp(u->host, ou->host))
+           continue;
+       if (urlStrcmp(u->user, ou->user))
+           continue;
+       if (urlStrcmp(u->password, ou->password))
+           continue;
+       if (urlStrcmp(u->portstr, ou->portstr))
+           continue;
+       break;  /* Found item in cache */
+    }
+
+    if (i == uCount) {
+       if (empty == NULL) {
+           uCount++;
+           if (uCache)
+               uCache = realloc(uCache, sizeof(*uCache) * uCount);
+           else
+               uCache = malloc(sizeof(*uCache));
+           empty = &uCache[i];
+       }
+       *empty = u;
+    } else {
+       /* Swap original url and path into the cached structure */
+       const char *up = uCache[i]->path;
+       uCache[i]->path = u->path;
+       u->path = up;
+       up = uCache[i]->url;
+       uCache[i]->url = u->url;
+       u->url = up;
+       freeUrlinfo(u);
+    }
+
+    /* This URL is now cached. */
+    *uret = u = uCache[i];
+
+    /* Zap proxy host and port in case they have been reset */
+    u->proxyp = -1;
+    FREE(u->proxyh);
+
+    /* Perform one-time FTP initialization */
+    if (!strcmp(u->service, "ftp")) {
+
+       if (mustAsk || (u->user != NULL && u->password == NULL)) {
+           char * prompt;
+           FREE(u->password);
+           prompt = alloca(strlen(u->host) + strlen(u->user) + 40);
+           sprintf(prompt, _("Password for %s@%s: "), u->user, u->host);
+           u->password = strdup(getpass(prompt));
+       }
+
+       if (u->proxyh == NULL) {
+           const char *proxy = rpmExpand("%{_ftpproxy}", NULL);
+           if (proxy && *proxy != '%') {
+               const char *uu = (u->user ? u->user : "anonymous");
+               char *nu = malloc(strlen(uu) + sizeof("@") + strlen(u->host));
+               strcpy(nu, uu);
+               strcat(nu, "@");
+               strcat(nu, u->host);
+               u->proxyu = nu;
+               u->proxyh = strdup(proxy);
+           }
+           xfree(proxy);
+       }
+
+       if (u->proxyp < 0) {
+           const char *proxy = rpmExpand("%{_ftpport}", NULL);
+           if (proxy && *proxy != '%') {
+               char *end;
+               int port = strtol(proxy, &end, 0);
+               if (!(end && *end == '\0')) {
+                   fprintf(stderr, _("error: %sport must be a number\n"),
+                       u->service);
+                   return;
+               }
+               u->proxyp = port;
+           }
+           xfree(proxy);
+       }
+    }
+
+    /* Perform one-time HTTP initialization */
+    if (!strcmp(u->service, "http")) {
+
+       if (u->proxyh == NULL) {
+           const char *proxy = rpmExpand("%{_httpproxy}", NULL);
+           if (proxy && *proxy != '%')
+               u->proxyh = strdup(proxy);
+           xfree(proxy);
+       }
+
+       if (u->proxyp < 0) {
+           const char *proxy = rpmExpand("%{_httpport}", NULL);
+           if (proxy && *proxy != '%') {
+               char *end;
+               int port = strtol(proxy, &end, 0);
+               if (!(end && *end == '\0')) {
+                   fprintf(stderr, _("error: %sport must be a number\n"),
+                       u->service);
+                   return;
+               }
+               u->proxyp = port;
+           }
+           xfree(proxy);
+       }
+
+    }
+
+    return;
+}
+
+/*
+ * Split URL into components. The URL can look like
+ *     service://user:password@host:port/path
+ */
+int urlSplit(const char * url, urlinfo **uret)
+{
+    urlinfo *u;
+    char *myurl;
+    char *s, *se, *f, *fe;
+
+    if (uret == NULL)
+       return -1;
+    if ((u = newUrlinfo()) == NULL)
+       return -1;
+
+    if ((se = s = myurl = strdup(url)) == NULL) {
+       freeUrlinfo(u);
+       return -1;
+    }
+
+    u->url = strdup(url);
+
+    do {
+       /* Point to end of next item */
+       while (*se && *se != '/') se++;
+       if (*se == '\0') {
+           /* XXX can't find path */
+           if (myurl) free(myurl);
+           freeUrlinfo(u);
+           return -1;
+       }
+       /* Item was service. Save service and go for the rest ...*/
+       if ((se != s) && se[-1] == ':' && se[0] == '/' && se[1] == '/') {
+               se[-1] = '\0';
+           u->service = strdup(s);
+           se += 2;    /* skip over "//" */
+           s = se++;
+           continue;
+       }
+       
+       /* Item was everything-but-path. Save path and continue parse on rest */
+       u->path = strdup(se);
+       *se = '\0';
+       break;
+    } while (1);
+
+    /* Look for ...@host... */
+    fe = f = s;
+    while (*fe && *fe != '@') fe++;
+    if (*fe == '@') {
+       s = fe + 1;
+       *fe = '\0';
+       /* Look for user:password@host... */
+       while (fe > f && *fe != ':') fe--;
+       if (*fe == ':') {
+           *fe++ = '\0';
+           u->password = strdup(fe);
+       }
+       u->user = strdup(f);
+    }
+
+    /* Look for ...host:port */
+    fe = f = s;
+    while (*fe && *fe != ':') fe++;
+    if (*fe == ':') {
+       *fe++ = '\0';
+       u->portstr = strdup(fe);
+       if (u->portstr != NULL && u->portstr[0] != '\0') {
+           char *end;
+           u->port = strtol(u->portstr, &end, 0);
+           if (!(end && *end == '\0')) {
+               rpmMessage(RPMMESS_ERROR, _("url port must be a number\n"));
+               if (myurl) free(myurl);
+               freeUrlinfo(u);
+               return -1;
+           }
+       }
+    }
+    u->host = strdup(f);
+
+    if (u->port < 0 && u->service != NULL) {
+       struct servent *se;
+       if ((se = getservbyname(u->service, "tcp")) != NULL)
+           u->port = ntohs(se->s_port);
+       else if (!strcasecmp(u->service, "ftp"))
+           u->port = IPPORT_FTP;
+       else if (!strcasecmp(u->service, "http"))
+           u->port = IPPORT_HTTP;
+    }
+
+    if (myurl) free(myurl);
+    if (uret) {
+       *uret = u;
+       findUrlinfo(uret, 0);
+    }
+    return 0;
+}
+
+static int urlConnect(const char * url, urlinfo ** uret)
+{
+    urlinfo *u;
+
+    if (urlSplit(url, &u) < 0)
+       return -1;
+
+    if (!strcmp(u->service, "ftp") && u->ftpControl < 0) {
+
+       u->ftpGetFileDoneNeeded = 0;    /* XXX PARANOIA */
+       rpmMessage(RPMMESS_DEBUG, _("logging into %s as %s, pw %s\n"),
+               u->host,
+               u->user ? u->user : "ftp",
+               u->password ? u->password : "(username)");
+
+       u->ftpControl = ftpOpen(u);
+
+       if (u->ftpControl < 0) {        /* XXX save ftpOpen error */
+           u->openError = u->ftpControl;
+           return u->ftpControl;
+       }
+
+    }
+
+    if (uret != NULL)
+       *uret = u;
+
+    return 0;
+}
+
+urltype urlIsURL(const char * url)
+{
+    struct urlstring *us;
+
+    for (us = urlstrings; us->leadin != NULL; us++) {
+       if (strncmp(url, us->leadin, strlen(us->leadin)))
+           continue;
+       return us->ret;
+    }
+
+    return URL_IS_UNKNOWN;
+}
+
+#ifdef NOTYET
+int urlAbort(FD_t fd)
+{
+    if (fd != NULL && fd->fd_url) {
+       urlinfo *u = (urlinfo *)fd->fd_url;
+       if (u->ftpControl >= 0)
+           ftpAbort(fd);
+    }
+}
+#endif
+
+int ufdClose(FD_t fd)
+{
+    if (fd != NULL && fd->fd_url) {
+       urlinfo *u = (urlinfo *)fd->fd_url;
+       /* Close the ftp control channel (not strictly necessary, but ... */
+       if (u->ftpControl >= 0) {
+           ftpAbort(fd);
+           fd = NULL;  /* XXX ftpAbort does fdClose(fd) */
+           close(u->ftpControl);
+           u->ftpControl = -1;
+       }
+    }
+    return fdClose(fd);
+}
+
+FD_t ufdOpen(const char *url, int flags, mode_t mode)
+{
+    FD_t fd = NULL;
+    urlinfo *u;
+
+    switch (urlIsURL(url)) {
+    case URL_IS_FTP:
+       if (urlConnect(url, &u) < 0)
+           break;
+       if ((fd = fdNew()) == NULL)
+           break;
+       fd->fd_url = u;
+       if ((u->openError = ftpGetFileDesc(fd)) < 0) {
+           u->ftpControl = -1;
+           fd = NULL;  /* XXX fd already closed */
+       }
+       break;
+    case URL_IS_HTTP:
+       if (urlSplit(url, &u))
+           break;
+       if ((fd = fdNew()) == NULL)
+           break;
+       fd->fd_url = u;
+       fd->fd_fd = httpOpen(u);
+       if (fd->fd_fd < 0)              /* XXX Save httpOpen error */
+           u->openError = fd->fd_fd;
+       break;
+    case URL_IS_PATH:
+       if (urlSplit(url, &u))
+           break;
+       fd = fdOpen(u->path, flags, mode);
+       break;
+    case URL_IS_DASH:
+       fd = fdDup(STDIN_FILENO);
+       break;
+    case URL_IS_UNKNOWN:
+    default:
+       fd = fdOpen(url, flags, mode);
+       break;
+    }
+
+    if (fd == NULL || fdFileno(fd) < 0) {
+       ufdClose(fd);
+       return NULL;
+    }
+    return fd;
+}
+
+int urlGetFile(const char * url, const char * dest) {
+    int rc;
+    FD_t sfd = NULL;
+    FD_t tfd = NULL;
+
+    sfd = ufdOpen(url, O_RDONLY, 0);
+    if (sfd == NULL || fdFileno(sfd) < 0) {
+       rpmMessage(RPMMESS_DEBUG, _("failed to open %s\n"), url);
+       ufdClose(sfd);
+       return FTPERR_UNKNOWN;
+    }
+
+    if (sfd->fd_url != NULL && dest == NULL) {
+       const char *fileName = ((urlinfo *)sfd->fd_url)->path;
+       if ((dest = strrchr(fileName, '/')) != NULL)
+           dest++;
+       else
+           dest = fileName;
+    }
+
+    tfd = fdOpen(dest, O_CREAT|O_WRONLY|O_TRUNC, 0600);
+    if (fdFileno(tfd) < 0) {
+       rpmMessage(RPMMESS_DEBUG, _("failed to create %s\n"), dest);
+       fdClose(tfd);
+       ufdClose(sfd);
+       return FTPERR_UNKNOWN;
+    }
+
+    switch (urlIsURL(url)) {
+    case URL_IS_FTP:
+       if ((rc = ftpGetFile(sfd, tfd))) {
+           unlink(dest);
+           ufdClose(sfd);
+       }
+       /* XXX fdClose(sfd) done by copyData */
+       break;
+    case URL_IS_HTTP:
+    case URL_IS_PATH:
+    case URL_IS_DASH:
+       if ((rc = httpGetFile(sfd, tfd))) {
+           unlink(dest);
+           ufdClose(sfd);
+       }
+       /* XXX fdClose(sfd) done by copyData */
+       break;
+    case URL_IS_UNKNOWN:
+    default:
+       rc = FTPERR_UNKNOWN;
+       break;
+    }
+
+    fdClose(tfd);
+
+    return rc;
+}
+
+/* XXX This only works for httpOpen/ftpOpen/ftpGetFileDesc failures */
+const char *urlStrerror(const char *url)
+{
+    urlinfo *u;
+    if (urlSplit(url, &u))
+       return "Malformed URL";
+    return ftpStrerror(u->openError);
+}