[BACK]Return to http.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ftp

Diff for /src/usr.bin/ftp/Attic/http.c between version 1.3 and 1.4

version 1.3, 1997/02/03 06:08:07 version 1.4, 2019/05/12 20:44:39
Line 1 
Line 1 
 /*      $OpenBSD$       */  
   
 /*  /*
  * Copyright (c) 1996 Theo de Raadt   * Copyright (c) 2015 Sunil Nimmagadda <sunil@openbsd.org>
  * Copyright (c) 1996 Brian Mitchell   * Copyright (c) 2012 - 2015 Reyk Floeter <reyk@openbsd.org>
  *   *
  * Redistribution and use in source and binary forms, with or without   * Permission to use, copy, modify, and distribute this software for any
  * modification, are permitted provided that the following conditions   * purpose with or without fee is hereby granted, provided that the above
  * are met:   * copyright notice and this permission notice appear in all copies.
  * 1. Redistributions of source code must retain the above copyright  
  *    notice, this list of conditions and the following disclaimer.  
  * 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   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  * 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.  
  */   */
   
 #include <sys/types.h>  
 #include <sys/file.h>  
 #include <sys/socket.h>  
 #include <netinet/in.h>  
   
 #include <ctype.h>  
 #include <err.h>  #include <err.h>
 #include <netdb.h>  #include <fcntl.h>
 #include <pwd.h>  #include <libgen.h>
 #include <signal.h>  #include <limits.h>
 #include <stdio.h>  #include <stdio.h>
   #include <stdint.h>
 #include <stdlib.h>  #include <stdlib.h>
 #include <string.h>  #include <string.h>
   #include <strings.h>
 #include <unistd.h>  #include <unistd.h>
   #ifndef NOSSL
   #include <tls.h>
   #endif
   
   #include "ftp.h"
   #include "xmalloc.h"
   
   #define MAX_REDIRECTS   10
   
   #ifndef NOSSL
   #define MINBUF          128
   
   static struct tls_config        *tls_config;
   static struct tls               *ctx;
   static int                       tls_session_fd = -1;
   static char * const              tls_verify_opts[] = {
   #define HTTP_TLS_CAFILE         0
           "cafile",
   #define HTTP_TLS_CAPATH         1
           "capath",
   #define HTTP_TLS_CIPHERS        2
           "ciphers",
   #define HTTP_TLS_DONTVERIFY     3
           "dont",
   #define HTTP_TLS_VERIFYDEPTH    4
           "depth",
   #define HTTP_TLS_MUSTSTAPLE     5
           "muststaple",
   #define HTTP_TLS_NOVERIFYTIME   6
           "noverifytime",
   #define HTTP_TLS_SESSION        7
           "session",
   #define HTTP_TLS_DOVERIFY       8
           "do",
           NULL
   };
   #endif /* NOSSL */
   
 /*  /*
  * This function lets you retrieve files from the WWW. It will accept   * HTTP status codes based on IANA assignments (2014-06-11 version):
  * any http:// url. It conects and retrieves the file, saving it in   * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  * the current directory.   * plus legacy (306) and non-standard (420).
  *  
  * Limitations:  
  * http://host does not work, nor http://host/ - you have to specifically  
  * specify the filename you want to transfer.  
  */   */
 int  static struct http_status {
 http_fetch(url)          int              code;
         char *url;          const char      *name;
   } http_status[] = {
           { 100,  "Continue" },
           { 101,  "Switching Protocols" },
           { 102,  "Processing" },
           /* 103-199 unassigned */
           { 200,  "OK" },
           { 201,  "Created" },
           { 202,  "Accepted" },
           { 203,  "Non-Authoritative Information" },
           { 204,  "No Content" },
           { 205,  "Reset Content" },
           { 206,  "Partial Content" },
           { 207,  "Multi-Status" },
           { 208,  "Already Reported" },
           /* 209-225 unassigned */
           { 226,  "IM Used" },
           /* 227-299 unassigned */
           { 300,  "Multiple Choices" },
           { 301,  "Moved Permanently" },
           { 302,  "Found" },
           { 303,  "See Other" },
           { 304,  "Not Modified" },
           { 305,  "Use Proxy" },
           { 306,  "Switch Proxy" },
           { 307,  "Temporary Redirect" },
           { 308,  "Permanent Redirect" },
           /* 309-399 unassigned */
           { 400,  "Bad Request" },
           { 401,  "Unauthorized" },
           { 402,  "Payment Required" },
           { 403,  "Forbidden" },
           { 404,  "Not Found" },
           { 405,  "Method Not Allowed" },
           { 406,  "Not Acceptable" },
           { 407,  "Proxy Authentication Required" },
           { 408,  "Request Timeout" },
           { 409,  "Conflict" },
           { 410,  "Gone" },
           { 411,  "Length Required" },
           { 412,  "Precondition Failed" },
           { 413,  "Payload Too Large" },
           { 414,  "URI Too Long" },
           { 415,  "Unsupported Media Type" },
           { 416,  "Range Not Satisfiable" },
           { 417,  "Expectation Failed" },
           { 418,  "I'm a teapot" },
           /* 419-421 unassigned */
           { 420,  "Enhance Your Calm" },
           { 422,  "Unprocessable Entity" },
           { 423,  "Locked" },
           { 424,  "Failed Dependency" },
           /* 425 unassigned */
           { 426,  "Upgrade Required" },
           /* 427 unassigned */
           { 428,  "Precondition Required" },
           { 429,  "Too Many Requests" },
           /* 430 unassigned */
           { 431,  "Request Header Fields Too Large" },
           /* 432-450 unassigned */
           { 451,  "Unavailable For Legal Reasons" },
           /* 452-499 unassigned */
           { 500,  "Internal Server Error" },
           { 501,  "Not Implemented" },
           { 502,  "Bad Gateway" },
           { 503,  "Service Unavailable" },
           { 504,  "Gateway Timeout" },
           { 505,  "HTTP Version Not Supported" },
           { 506,  "Variant Also Negotiates" },
           { 507,  "Insufficient Storage" },
           { 508,  "Loop Detected" },
           /* 509 unassigned */
           { 510,  "Not Extended" },
           { 511,  "Network Authentication Required" },
           /* 512-599 unassigned */
           { 0,    NULL },
           };
   
   struct http_headers {
           char    *location;
           off_t    content_length;
           int      chunked;
   };
   
   static void              decode_chunk(int, uint, FILE *);
   static char             *header_lookup(const char *, const char *);
   static const char       *http_error(int);
   static void              http_headers_free(struct http_headers *);
   static ssize_t           http_getline(int, char **, size_t *);
   static size_t            http_read(int, char *, size_t);
   static struct url       *http_redirect(struct url *, char *);
   static void              http_save_chunks(struct url *, FILE *, off_t *);
   static int               http_status_cmp(const void *, const void *);
   static int               http_request(int, const char *,
                               struct http_headers **);
   static char             *relative_path_resolve(const char *, const char *);
   
   #ifndef NOSSL
   static void              tls_copy_file(struct url *, FILE *, off_t *);
   static ssize_t           tls_getline(char **, size_t *, struct tls *);
   #endif
   
   static FILE     *fp;
   
   void
   http_connect(struct url *url, struct url *proxy, int timeout)
 {  {
         char *hostname, *filename, basename[MAXPATHLEN] = "/";          const char      *host, *port;
         char buf[8192], *bufp, *req = NULL;          int              sock;
         struct sockaddr_in addr;  
         struct hostent *he;  
         FILE *write_to;  
         char *s, *p;  
         int bytes, c, d;  
         int sock = -1, file = -1, ret = 1;  
   
         s = url + strlen("http://");          host = proxy ? proxy->host : url->host;
         p = strchr(s, '/');          port = proxy ? proxy->port : url->port;
         if (p)          if ((sock = tcp_connect(host, port, timeout)) == -1)
                 *p = '\0';                  exit(1);
         else  
                 p = s + strlen(s);          if ((fp = fdopen(sock, "r+")) == NULL)
         if (p - s > MAXHOSTNAMELEN-1) {                  err(1, "%s: fdopen", __func__);
                 warn("hostname too long");  
                 return (1);  #ifndef NOSSL
           struct http_headers     *headers;
           char                    *auth = NULL, *req;
           int                      authlen = 0, code;
   
           if (url->scheme != S_HTTPS)
                   return;
   
           if (proxy) {
                   if (url->basic_auth)
                           authlen = xasprintf(&auth,
                               "Proxy-Authorization: Basic %s\r\n",
                               url->basic_auth);
   
                   xasprintf(&req,
                       "CONNECT %s:%s HTTP/1.0\r\n"
                       "User-Agent: %s\r\n"
                       "%s"
                       "\r\n",
                       url->host, url->port,
                       useragent,
                       url->basic_auth ? auth : "");
   
                   freezero(auth, authlen);
                   if ((code = http_request(S_HTTP, req, &headers)) != 200)
                           errx(1, "%s: failed to CONNECT to %s:%s: %s",
                               __func__, url->host, url->port, http_error(code));
   
                   free(req);
                   http_headers_free(headers);
         }          }
         hostname = s;  
         filename = p + 1;  
   
         p = strrchr(filename, '/');          if ((ctx = tls_client()) == NULL)
         if (p == NULL) {                  errx(1, "failed to create tls client");
                 if (strlen(s) > MAXPATHLEN-1) {  
                         warn("filename too long");          if (tls_configure(ctx, tls_config) != 0)
                         return (1);                  errx(1, "%s: %s", __func__, tls_error(ctx));
   
           if (tls_connect_socket(ctx, sock, url->host) != 0)
                   errx(1, "%s: %s", __func__, tls_error(ctx));
   #endif /* NOSSL */
   }
   
   struct url *
   http_get(struct url *url, struct url *proxy, off_t *offset, off_t *sz)
   {
           struct http_headers     *headers;
           char                    *auth = NULL, *path = NULL, *range = NULL, *req;
           int                      authlen = 0, code, redirects = 0;
   
    redirected:
           log_request("Requesting", url, proxy);
           if (*offset)
                   xasprintf(&range, "Range: bytes=%lld-\r\n", *offset);
   
           if (url->basic_auth)
                   authlen = xasprintf(&auth, "Authorization: Basic %s\r\n",
                       url->basic_auth);
   
           if (proxy && url->scheme != S_HTTPS)
                   path = url_str(url);
           else if (url->path)
                   path = url_encode(url->path);
   
           xasprintf(&req,
               "GET %s HTTP/1.1\r\n"
               "Host: %s\r\n"
               "%s"
               "%s"
               "Connection: close\r\n"
               "User-Agent: %s\r\n"
               "\r\n",
               path ? path : "/",
               url->host,
               *offset ? range : "",
               url->basic_auth ? auth : "",
               useragent);
           code = http_request(url->scheme, req, &headers);
           freezero(auth, authlen);
           free(range);
           free(path);
           free(req);
           switch (code) {
           case 200:
                   if (*offset) {
                           warnx("Server does not support resume.");
                           *offset = 0;
                 }                  }
                 strcpy(basename, filename);                  break;
         } else {          case 206:
                 p++;                  break;
                 if (strlen(p) > MAXPATHLEN-1) {          case 301:
                         warn("filename too long");          case 302:
                         return (1);          case 303:
                 }          case 307:
                 strcpy(basename, p);                  http_close(url);
                   if (++redirects > MAX_REDIRECTS)
                           errx(1, "Too many redirections requested");
   
                   if (headers->location == NULL)
                           errx(1, "%s: Location header missing", __func__);
   
                   url = http_redirect(url, headers->location);
                   http_headers_free(headers);
                   log_request("Redirected to", url, proxy);
                   http_connect(url, proxy, 0);
                   goto redirected;
           case 416:
                   errx(1, "File is already fully retrieved.");
                   break;
           default:
                   errx(1, "Error retrieving file: %d %s", code, http_error(code));
         }          }
         if (strlen(basename) == 0)  
                 strcpy(basename, "index.html");  
   
         req = (char *)malloc(sizeof("GET ") + strlen(filename) +3);          *sz = headers->content_length + *offset;
         if (!req) {          url->chunked = headers->chunked;
                 warn("no memory");          http_headers_free(headers);
                 return (1);          return url;
   }
   
   void
   http_save(struct url *url, FILE *dst_fp, off_t *offset)
   {
           if (url->chunked)
                   http_save_chunks(url, dst_fp, offset);
   #ifndef NOSSL
           else if (url->scheme == S_HTTPS)
                   tls_copy_file(url, dst_fp, offset);
   #endif
           else
                   copy_file(dst_fp, fp, offset);
   }
   
   static struct url *
   http_redirect(struct url *old_url, char *location)
   {
           struct url      *new_url;
   
           /* absolute uri reference */
           if (strncasecmp(location, "http", 4) == 0 ||
               strncasecmp(location, "https", 5) == 0) {
                   if ((new_url = url_parse(location)) == NULL)
                           exit(1);
   
                   goto done;
         }          }
         sprintf(req, "GET /%s\n", filename);  
   
         he = gethostbyname(hostname);          /* relative uri reference */
         if (!he) {          new_url = xcalloc(1, sizeof *new_url);
                 herror("gethostbyname");          new_url->scheme = old_url->scheme;
                 goto die;          new_url->host = xstrdup(old_url->host);
           new_url->port = xstrdup(old_url->port);
   
           /* absolute-path reference */
           if (location[0] == '/')
                   new_url->path = xstrdup(location);
           else
                   new_url->path = relative_path_resolve(old_url->path, location);
   
    done:
           new_url->fname = xstrdup(old_url->fname);
           url_free(old_url);
           return new_url;
   }
   
   static char *
   relative_path_resolve(const char *base_path, const char *location)
   {
           char    *new_path, *p;
   
           /* trim fragment component from both uri */
           if ((p = strchr(location, '#')) != NULL)
                   *p = '\0';
           if (base_path && (p = strchr(base_path, '#')) != NULL)
                   *p = '\0';
   
           if (base_path == NULL)
                   xasprintf(&new_path, "/%s", location);
           else if (base_path[strlen(base_path) - 1] == '/')
                   xasprintf(&new_path, "%s%s", base_path, location);
           else {
                   p = dirname(base_path);
                   xasprintf(&new_path, "%s/%s",
                       strcmp(p, ".") == 0 ? "" : p, location);
         }          }
   
         sock = socket(AF_INET, SOCK_STREAM, 0);          return new_path;
         if (sock == -1) {  }
                 perror("socket");  
                 goto die;  static void
   http_save_chunks(struct url *url, FILE *dst_fp, off_t *offset)
   {
           char    *buf = NULL;
           size_t   n = 0;
           uint     chunk_sz;
   
           http_getline(url->scheme, &buf, &n);
           if (sscanf(buf, "%x", &chunk_sz) != 1)
                   errx(1, "%s: Failed to get chunk size", __func__);
   
           while (chunk_sz > 0) {
                   decode_chunk(url->scheme, chunk_sz, dst_fp);
                   *offset += chunk_sz;
                   http_getline(url->scheme, &buf, &n);
                   if (sscanf(buf, "%x", &chunk_sz) != 1)
                           errx(1, "%s: Failed to get chunk size", __func__);
         }          }
   
         memset(&addr, 0, sizeof addr);          free(buf);
         addr.sin_family = he->h_addrtype;  }
         addr.sin_port = htons(80);  
         memcpy(&addr.sin_addr, he->h_addr, he->h_length);  
   
         if (connect(sock, (struct sockaddr *)&addr, sizeof addr) == -1) {  static void
                 perror("connect");  decode_chunk(int scheme, uint sz, FILE *dst_fp)
                 goto die;  {
           size_t  bufsz;
           size_t  r;
           char    buf[BUFSIZ], crlf[2];
   
           bufsz = sizeof(buf);
           while (sz > 0) {
                   if (sz < bufsz)
                           bufsz = sz;
   
                   r = http_read(scheme, buf, bufsz);
                   if (fwrite(buf, 1, r, dst_fp) != r)
                           errx(1, "%s: fwrite", __func__);
   
                   sz -= r;
         }          }
         printf("Connected to %s.\n", hostname);  
   
         printf("Retrieving using: %s", req);          /* CRLF terminating the chunk */
         for (bufp = req, c = strlen(bufp); c > 0; c -= d, bufp += d) {          if (http_read(scheme, crlf, sizeof(crlf)) != sizeof(crlf))
                 if ((d = write(sock, bufp, c)) <= 0)                  errx(1, "%s: Failed to read terminal crlf", __func__);
                         break;  
           if (crlf[0] != '\r' || crlf[1] != '\n')
                   errx(1, "%s: Invalid chunked encoding", __func__);
   }
   
   void
   http_close(struct url *url)
   {
   #ifndef NOSSL
           ssize_t r;
   
           if (url->scheme == S_HTTPS) {
                   if (tls_session_fd != -1)
                           dprintf(STDERR_FILENO, "tls session resumed: %s\n",
                               tls_conn_session_resumed(ctx) ? "yes" : "no");
   
                   do {
                           r = tls_close(ctx);
                   } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
                   tls_free(ctx);
         }          }
         if (d == -1) {  
                 perror("sending command");  #endif
                 goto die;          fclose(fp);
   }
   
   static int
   http_request(int scheme, const char *req, struct http_headers **hdrs)
   {
           struct http_headers     *headers;
           const char              *e;
           char                    *buf = NULL, *p;
           size_t                   n = 0;
           ssize_t                  buflen;
           uint                     code;
   #ifndef NOSSL
           ssize_t                  nw;
   #endif
   
           if (io_debug)
                   fprintf(stderr, "<<< %s", req);
   
           switch (scheme) {
   #ifndef NOSSL
           case S_HTTPS:
                   do {
                           nw = tls_write(ctx, req, strlen(req));
                   } while (nw == TLS_WANT_POLLIN || nw == TLS_WANT_POLLOUT);
                   if (nw == -1)
                           errx(1, "%s: tls_write: %s", __func__, tls_error(ctx));
                   break;
   #endif
           case S_FTP:
           case S_HTTP:
                   if (fprintf(fp, "%s", req) < 0)
                           errx(1, "%s: fprintf", __func__);
                   (void)fflush(fp);
                   break;
         }          }
   
         file = open(basename, O_CREAT|O_WRONLY|O_TRUNC, 0666);          http_getline(scheme, &buf, &n);
         if (!file) {          if (io_debug)
                 perror("fopen");                  fprintf(stderr, ">>> %s", buf);
                 goto die;  
           if (sscanf(buf, "%*s %u %*s", &code) != 1)
                   errx(1, "%s: failed to extract status code", __func__);
   
           if (code < 100 || code > 511)
                   errx(1, "%s: invalid status code %d", __func__, code);
   
           headers = xcalloc(1, sizeof *headers);
           for (;;) {
                   buflen = http_getline(scheme, &buf, &n);
                   buflen -= 1;
                   if (buflen > 0 && buf[buflen - 1] == '\r')
                           buflen -= 1;
                   buf[buflen] = '\0';
   
                   if (io_debug)
                           fprintf(stderr, ">>> %s\n", buf);
   
                   if (buflen == 0)
                           break; /* end of headers */
   
                   if ((p = header_lookup(buf, "Content-Length:")) != NULL) {
                           headers->content_length = strtonum(p, 0, INT64_MAX, &e);
                           if (e)
                                   err(1, "%s: Content-Length is %s: %lld",
                                       __func__, e, headers->content_length);
                   }
   
                   if ((p = header_lookup(buf, "Location:")) != NULL)
                           headers->location = xstrdup(p);
   
                   if ((p = header_lookup(buf, "Transfer-Encoding:")) != NULL)
                           if (strcasestr(p, "chunked") != NULL)
                                   headers->chunked = 1;
   
         }          }
   
         bytes = 0;          *hdrs = headers;
         while ((c = read(sock, buf, sizeof (buf))) > 0) {          free(buf);
                 bytes += c;          return code;
                 for (bufp = buf; c > 0; c -= d, bufp += d)  }
                         if ((d = write(file, bufp, c)) <= 0)  
                                 break;  static void
   http_headers_free(struct http_headers *headers)
   {
           if (headers == NULL)
                   return;
   
           free(headers->location);
           free(headers);
   }
   
   static char *
   header_lookup(const char *buf, const char *key)
   {
           char    *p;
   
           if (strncasecmp(buf, key, strlen(key)) == 0) {
                   if ((p = strchr(buf, ' ')) == NULL)
                           errx(1, "Failed to parse %s", key);
                   return ++p;
         }          }
         if (d == -1) {  
                 perror("failed to receive correctly");          return NULL;
                 goto die;  }
   
   static const char *
   http_error(int code)
   {
           struct http_status      error, *res;
   
           /* Set up key */
           error.code = code;
   
           if ((res = bsearch(&error, http_status,
               sizeof(http_status) / sizeof(http_status[0]) - 1,
               sizeof(http_status[0]), http_status_cmp)) != NULL)
                   return (res->name);
   
           return (NULL);
   }
   
   static int
   http_status_cmp(const void *a, const void *b)
   {
           const struct http_status *ea = a;
           const struct http_status *eb = b;
   
           return (ea->code - eb->code);
   }
   
   
   static ssize_t
   http_getline(int scheme, char **buf, size_t *n)
   {
           ssize_t buflen;
   
           switch (scheme) {
   #ifndef NOSSL
           case S_HTTPS:
                   if ((buflen = tls_getline(buf, n, ctx)) == -1)
                           errx(1, "%s: tls_getline", __func__);
                   break;
   #endif
           case S_FTP:
           case S_HTTP:
                   if ((buflen = getline(buf, n, fp)) == -1)
                           err(1, "%s: getline", __func__);
                   break;
           default:
                   errx(1, "%s: invalid scheme", __func__);
         }          }
         printf("Success, closing connection.\n");  
         ret = 0;          return buflen;
 die:  }
         if (sock != -1)  
                 close(sock);  static size_t
         if (file != -1)  http_read(int scheme, char *buf, size_t size)
                 close(file);  {
         if (req)          size_t  r;
                 free(req);  #ifndef NOSSL
         return (ret);          ssize_t rs;
 }  #endif
   
           switch (scheme) {
   #ifndef NOSSL
           case S_HTTPS:
                   do {
                           rs = tls_read(ctx, buf, size);
                   } while (rs == TLS_WANT_POLLIN || rs == TLS_WANT_POLLOUT);
                   if (rs == -1)
                           errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
                   r = rs;
                   break;
   #endif
           case S_HTTP:
                   if ((r = fread(buf, 1, size, fp)) < size)
                           if (!feof(fp))
                                   errx(1, "%s: fread", __func__);
                   break;
           default:
                   errx(1, "%s: invalid scheme", __func__);
           }
   
           return r;
   }
   
   #ifndef NOSSL
   void
   https_init(char *tls_options)
   {
           char            *str;
           int              depth;
           const char      *ca_file, *errstr;
   
           if (tls_init() != 0)
                   errx(1, "tls_init failed");
   
           if ((tls_config = tls_config_new()) == NULL)
                   errx(1, "tls_config_new failed");
   
           if (tls_config_set_ciphers(tls_config, "legacy") != 0)
                   errx(1, "tls set ciphers failed: %s",
                       tls_config_error(tls_config));
   
           ca_file = tls_default_ca_cert_file();
           while (tls_options && *tls_options) {
                   switch (getsubopt(&tls_options, tls_verify_opts, &str)) {
                   case HTTP_TLS_CAFILE:
                           if (str == NULL)
                                   errx(1, "missing CA file");
                           ca_file = str;
                           break;
                   case HTTP_TLS_CAPATH:
                           if (str == NULL)
                                   errx(1, "missing ca path");
                           if (tls_config_set_ca_path(tls_config, str) != 0)
                                   errx(1, "tls ca path failed");
                           break;
                   case HTTP_TLS_CIPHERS:
                           if (str == NULL)
                                   errx(1, "missing cipher list");
                           if (tls_config_set_ciphers(tls_config, str) != 0)
                                   errx(1, "tls set ciphers failed");
                           break;
                   case HTTP_TLS_DONTVERIFY:
                           tls_config_insecure_noverifycert(tls_config);
                           tls_config_insecure_noverifyname(tls_config);
                           break;
                   case HTTP_TLS_VERIFYDEPTH:
                           if (str == NULL)
                                   errx(1, "missing depth");
                           depth = strtonum(str, 0, INT_MAX, &errstr);
                           if (errstr)
                                   errx(1, "Cert validation depth is %s", errstr);
                           tls_config_set_verify_depth(tls_config, depth);
                           break;
                   case HTTP_TLS_MUSTSTAPLE:
                           tls_config_ocsp_require_stapling(tls_config);
                           break;
                   case HTTP_TLS_NOVERIFYTIME:
                           tls_config_insecure_noverifytime(tls_config);
                           break;
                   case HTTP_TLS_SESSION:
                           if (str == NULL)
                                   errx(1, "missing session file");
                           tls_session_fd = open(str, O_RDWR|O_CREAT, 0600);
                           if (tls_session_fd == -1)
                                   err(1, "failed to open or create session file "
                                       "'%s'", str);
                           if (tls_config_set_session_fd(tls_config,
                               tls_session_fd) == -1)
                                   errx(1, "failed to set session: %s",
                                       tls_config_error(tls_config));
                           break;
                   case HTTP_TLS_DOVERIFY:
                           /* For compatibility, we do verify by default */
                           break;
                   default:
                           errx(1, "Unknown -S suboption `%s'",
                               suboptarg ? suboptarg : "");
                   }
           }
   
           if (tls_config_set_ca_file(tls_config, ca_file) == -1)
                   errx(1, "tls_config_set_ca_file failed");
   }
   
   static ssize_t
   tls_getline(char **buf, size_t *buflen, struct tls *tls)
   {
           char            *newb;
           size_t           newlen, off;
           int              ret;
           unsigned char    c;
   
           if (buf == NULL || buflen == NULL)
                   return -1;
   
           /* If buf is NULL, we have to assume a size of zero */
           if (*buf == NULL)
                   *buflen = 0;
   
           off = 0;
           do {
                   do {
                           ret = tls_read(tls, &c, 1);
                   } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
                   if (ret == -1)
                           return -1;
   
                   /* Ensure we can handle it */
                   if (off + 2 > SSIZE_MAX)
                           return -1;
   
                   newlen = off + 2; /* reserve space for NUL terminator */
                   if (newlen > *buflen) {
                           newlen = newlen < MINBUF ? MINBUF : *buflen * 2;
                           newb = recallocarray(*buf, *buflen, newlen, 1);
                           if (newb == NULL)
                                   return -1;
   
                           *buf = newb;
                           *buflen = newlen;
                   }
   
                   *(*buf + off) = c;
                   off += 1;
           } while (c != '\n');
   
           *(*buf + off) = '\0';
           return off;
   }
   
   static void
   tls_copy_file(struct url *url, FILE *dst_fp, off_t *offset)
   {
           char    *tmp_buf;
           ssize_t  r;
   
           tmp_buf = xmalloc(TMPBUF_LEN);
           for (;;) {
                   do {
                           r = tls_read(ctx, tmp_buf, TMPBUF_LEN);
                   } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT);
   
                   if (r == -1)
                           errx(1, "%s: tls_read: %s", __func__, tls_error(ctx));
                   else if (r == 0)
                           break;
   
                   *offset += r;
                   if (fwrite(tmp_buf, 1, r, dst_fp) != (size_t)r)
                           err(1, "%s: fwrite", __func__);
           }
           free(tmp_buf);
   }
   #endif /* NOSSL */

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.4