aboutsummaryrefslogtreecommitdiff
path: root/networking/httpget.c
diff options
context:
space:
mode:
Diffstat (limited to 'networking/httpget.c')
-rw-r--r--networking/httpget.c1458
1 files changed, 1458 insertions, 0 deletions
diff --git a/networking/httpget.c b/networking/httpget.c
new file mode 100644
index 0000000..387a33f
--- /dev/null
+++ b/networking/httpget.c
@@ -0,0 +1,1458 @@
+/*
+ * Copyright (c) 2011-2013 RIPE NCC <atlas@ripe.net>
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * httpget.c -- Simple program that uses the HTTP GET command
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include "libbb.h"
+
+#define SAFE_PREFIX_OUT ATLAS_DATA_OUT
+#define SAFE_PREFIX_NEW ATLAS_DATA_NEW
+
+#define debug 0
+
+static struct option longopts[]=
+{
+ { "append", no_argument, NULL, 'a' },
+ { "get", no_argument, NULL, 'g' },
+ { "head", no_argument, NULL, 'E' },
+ { "post", no_argument, NULL, 'P' },
+ { "post-file", required_argument, NULL, 'p' },
+ { "post-dir", required_argument, NULL, 'D' },
+ { "post-header", required_argument, NULL, 'h' },
+ { "post-footer", required_argument, NULL, 'f' },
+ { "set-time", required_argument, NULL, 's' },
+ { "store-headers", required_argument, NULL, 'H' },
+ { "store-body", required_argument, NULL, 'B' },
+ { "summary", no_argument, NULL, 'S' },
+ { "user-agent", required_argument, NULL, 'u' },
+ { NULL, }
+};
+
+static char *time_tolerance;
+static char buffer[1024];
+static char host_addr[INET6_ADDRSTRLEN];
+static sa_family_t family;
+static const char *user_agent= "httpget for atlas.ripe.net";
+static int tcp_fd= -1;
+
+static int parse_url(char *url, char **hostp, char **portp, char **hostportp,
+ char **pathp);
+static int check_result(FILE *tcp_file, int *result);
+static int eat_headers(FILE *tcp_file, int *chunked, int *content_length,
+ int *headers_size, FILE *out_file, int max_headers);
+static int connect_to_name(char *host, char *port, int only_v4, int only_v6,
+ struct timeval *start_time, int *gerr);
+static char *do_dir(char *dir_name, off_t *lenp);
+static int copy_chunked(FILE *in_file, FILE *out_file, int *length,
+ int max_body);
+static int copy_bytes(FILE *in_file, FILE *out_file, int *length,
+ int max_body);
+static void got_alarm(int sig);
+static void fatal(const char *fmt, ...);
+static void fatal_err(const char *fmt, ...);
+static void report(const char *fmt, ...);
+static void report_err(const char *fmt, ...);
+static int write_to_tcp_fd (int fd, FILE *tcp_file);
+static void skip_spaces(const char *cp, char **ncp);
+
+int httpget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int httpget_main(int argc, char *argv[])
+{
+ int c, i, r, fd, fdF, fdH, fdS, chunked, content_length,
+ result, http_result, do_get, do_head, do_post,
+ max_headers, max_body, do_multiline, only_v4, only_v6,
+ do_summary, headers_size, no_body, do_append, do_http10, gerr,
+ out_file_needs_closing;
+ char *url, *host, *port, *hostport, *path, *filelist, *p, *check;
+ char *post_dir, *post_file, *output_file, *post_footer, *post_header,
+ *A_arg, *store_headers, *store_body;
+ FILE *tcp_file, *out_file;
+ struct timeval tv_start, tv_end;
+ struct stat sbF, sbH, sbS;
+ off_t cLength, dir_length;
+ struct sigaction sa;
+ char rndbuf[16];
+
+ /* Arguments */
+ do_http10= 0;
+ do_append= 0;
+ do_get= 1;
+ do_head= 0;
+ do_post= 0;
+ post_dir= NULL;
+ post_file= NULL;
+ post_footer=NULL;
+ post_header=NULL;
+ output_file= NULL;
+ time_tolerance= NULL;
+ store_headers= NULL;
+ store_body= NULL;
+ A_arg= NULL;
+ only_v4= 0;
+ only_v6= 0;
+ do_summary= 0;
+
+ /* Used in cleanup */
+ fd= -1;
+ fdH= -1;
+ fdF= -1;
+ fdS= -1;
+ tcp_fd= -1;
+ tcp_file= NULL;
+ out_file= NULL;
+ out_file_needs_closing= 0;
+ host= NULL;
+ port= NULL;
+ hostport= NULL;
+ path= NULL;
+ filelist= NULL;
+
+ /* Others */
+ do_multiline= 0;
+ http_result= -1;
+ dir_length= 0;
+ headers_size= 0;
+
+ /* Allow us to be called directly by another program in busybox */
+ optind= 0;
+ while (c= getopt_long(argc, argv, "01A:O:46?", longopts, NULL), c != -1)
+ {
+ switch(c)
+ {
+ case '0':
+ do_http10= 1;
+ break;
+ case '1':
+ do_http10= 0;
+ break;
+ case 'a': /* --append */
+ do_append= 1;
+ break;
+ case 'A':
+ A_arg= optarg;
+ break;
+ case 'O':
+ output_file= optarg;
+ break;
+ case 'g': /* --get */
+ do_get = 1;
+ do_head = 0;
+ do_post = 0;
+ break;
+ case 'E': /* --head */
+ do_get = 0;
+ do_head = 1;
+ do_post = 0;
+ break;
+ case 'P': /* --post */
+ do_get = 0;
+ do_head = 0;
+ do_post = 1;
+ break;
+ case 'D':
+ post_dir = optarg; /* --post-dir */
+ break;
+ case 'h': /* --post-header */
+ post_header= optarg;
+ break;
+ case 'f': /* --post-footer */
+ post_footer= optarg;
+ break;
+
+ case 'p': /* --post-file */
+ post_file= optarg;
+ break;
+ case 's': /* --set-time */
+ time_tolerance= optarg;
+ break;
+ case 'H': /* --store-headers */
+ store_headers= optarg;
+ break;
+ case 'B': /* --store-body */
+ store_body= optarg;
+ break;
+ case 'S': /* --summary */
+ do_summary= 1;
+ break;
+ case '4':
+ only_v4= 1;
+ only_v6= 0;
+ break;
+ case '6':
+ only_v6= 1;
+ only_v4= 0;
+ break;
+ case 'u': /* --user-agent */
+ user_agent= optarg;
+ break;
+ case '?':
+ bb_show_usage();
+ return 1;
+ default:
+ fatal("bad option '%c'", c);
+ }
+ }
+
+ if (optind != argc-1)
+ fatal("exactly one url expected");
+ url= argv[optind];
+
+ max_headers= 0;
+ max_body= UINT_MAX; /* default is to write out the entire body */
+ if (do_summary)
+ max_body= 0; /* default to no body if we want a summary */
+
+ if (store_headers)
+ {
+ max_headers= strtoul(store_headers, &check, 10);
+ if (check[0] != '\0')
+ {
+ report("unable to parse argument '%s'", store_headers);
+ return 1;
+ }
+ }
+
+ if (store_body)
+ {
+ max_body= strtoul(store_body, &check, 10);
+ if (check[0] != '\0')
+ {
+ report("unable to parse argument '%s'", store_body);
+ return 1;
+ }
+ }
+
+ if (!parse_url(url, &host, &port, &hostport, &path))
+ {
+ goto err;
+ }
+
+ //printf("host: %s\n", host);
+ //printf("port: %s\n", port);
+ //printf("hostport: %s\n", hostport);
+ //printf("path: %s\n", path);
+
+ if (post_dir)
+ {
+ filelist= do_dir(post_dir, &dir_length);
+ if (!filelist)
+ {
+ /* Something went wrong. */
+ goto err;
+ }
+ if (debug)
+ {
+ fprintf(stderr, "total size in dir: %ld\n",
+ (long)dir_length);
+ }
+ }
+
+ if(post_header != NULL )
+ {
+ if (!validate_filename(post_header, SAFE_PREFIX_OUT))
+ {
+ report("insecure file '%s'", post_header);
+ goto err;
+ }
+ fdH = open(post_header, O_RDONLY);
+ if(fdH == -1 )
+ {
+ report_err("unable to open header '%s'", post_header);
+ goto err;
+ }
+ if (fstat(fdH, &sbH) == -1)
+ {
+ report_err("fstat failed on header file '%s'",
+ post_header);
+ goto err;
+ }
+ if (!S_ISREG(sbH.st_mode))
+ {
+ report("'%s' header is not a regular file",
+ post_header);
+ goto err;
+ }
+ }
+
+ if(post_footer != NULL )
+ {
+ if (!validate_filename(post_footer, SAFE_PREFIX_OUT))
+ {
+ report("insecure file '%s'", post_footer);
+ goto err;
+ }
+ fdF = open(post_footer, O_RDONLY);
+ if(fdF == -1 )
+ {
+ report_err("unable to open footer '%s'", post_footer);
+ goto err;
+ }
+ if (fstat(fdF, &sbF) == -1)
+ {
+ report_err("fstat failed on footer file '%s'",
+ post_footer);
+ goto err;
+ }
+ if (!S_ISREG(sbF.st_mode))
+ {
+ report("'%s' footer is not a regular file",
+ post_footer);
+ goto err;
+ }
+ }
+
+ /* Try to open the file before trying to connect */
+ if (post_file != NULL)
+ {
+ if (!validate_filename(post_file, SAFE_PREFIX_OUT))
+ {
+ report("insecure file '%s'", post_file);
+ goto err;
+ }
+ fdS= open(post_file, O_RDONLY);
+ if (fdS == -1)
+ {
+ report_err("unable to open '%s'", post_file);
+ goto err;
+ }
+ if (fstat(fdS, &sbS) == -1)
+ {
+ report_err("fstat failed");
+ goto err;
+ }
+ if (!S_ISREG(sbS.st_mode))
+ {
+ report("'%s' is not a regular file", post_file);
+ goto err;
+ }
+ }
+
+ sa.sa_flags= 0;
+ sa.sa_handler= got_alarm;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGALRM, &sa, NULL);
+ if (debug) fprintf(stderr, "setting alarm\n");
+ alarm(10);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (output_file)
+ {
+ if (!validate_filename(output_file, SAFE_PREFIX_NEW))
+ {
+ report("insecure output file '%s'", output_file);
+ goto err;
+ }
+ out_file= fopen(output_file, do_append ? "a" : "w");
+ if (!out_file)
+ {
+ report_err("unable to create '%s'", output_file);
+ goto err;
+ }
+ out_file_needs_closing= 1;
+ }
+ else
+ out_file= stdout;
+
+
+ tcp_fd= connect_to_name(host, port, only_v4, only_v6, &tv_start,
+ &gerr);
+ if (tcp_fd == -1)
+ {
+ int s_errno= errno;
+
+ if (A_arg && do_summary)
+ {
+ fprintf(out_file, "%s %ld ",
+ A_arg, (long)time(NULL));
+ if (gerr != 0)
+ {
+ fprintf(out_file, "bad-hostname %s\n",
+ gai_strerror(gerr));
+ }
+ else
+ {
+ fprintf(out_file, "connect error %d\n",
+ s_errno);
+ }
+ }
+ report("unable to connect to '%s'", host);
+ goto err;
+ }
+
+ /* Stdio makes life easy */
+ tcp_file= fdopen(tcp_fd, "r+");
+ if (tcp_file == NULL)
+ {
+ report("fdopen failed");
+ goto err;
+ }
+ tcp_fd= -1;
+
+ if (debug) fprintf(stderr, "httpget: sending request\n");
+ fprintf(tcp_file, "%s %s HTTP/1.%c\r\n",
+ do_get ? "GET" : do_head ? "HEAD" : "POST", path,
+ do_http10 ? '0' : '1');
+ fprintf(tcp_file, "Host: %s\r\n", host);
+ fprintf(tcp_file, "Connection: close\r\n");
+ fprintf(tcp_file, "User-Agent: %s\r\n", user_agent);
+ if (do_post)
+ {
+ fprintf(tcp_file,
+ "Content-Type: application/x-www-form-urlencoded\r\n");
+ }
+
+ cLength= 0;
+ if( post_header != NULL )
+ cLength += sbH.st_size;
+
+ if (post_file)
+ cLength += sbS.st_size;
+
+ if (post_dir)
+ cLength += dir_length;
+
+ if( post_footer != NULL )
+ cLength += sbF.st_size;
+
+ fprintf(tcp_file, "Content-Length: %lu\r\n", (unsigned long)cLength);
+ fprintf(tcp_file, "\r\n");
+
+ if( post_header != NULL )
+ {
+ if (!write_to_tcp_fd(fdH, tcp_file))
+ {
+ printf("write_to_tcp_fd failed\n");
+ goto fail;
+ }
+ }
+
+ if (post_file != NULL)
+ {
+ if (!write_to_tcp_fd(fdS, tcp_file))
+ {
+ printf("write_to_tcp_fd failed\n");
+ goto fail;
+ }
+ }
+
+ if (post_dir)
+ {
+ for (p= filelist; p[0] != 0; p += strlen(p)+1)
+ {
+ if (debug) fprintf(stderr, "posting file '%s'\n", p);
+ if (!validate_filename(p, SAFE_PREFIX_OUT))
+ {
+ report("insecure file '%s'", p);
+ goto err;
+ }
+ fd= open(p, O_RDONLY);
+ if (fd == -1)
+ {
+ report_err("unable to open '%s'", p);
+ goto err;
+ }
+ r= write_to_tcp_fd(fd, tcp_file);
+ close(fd);
+ fd= -1;
+ if (!r)
+ {
+ printf("write_to_tcp_fd failed\n");
+ goto fail;
+ }
+ }
+ }
+
+ if( post_footer != NULL)
+ {
+ if (!write_to_tcp_fd(fdF, tcp_file))
+ {
+ printf("write_to_tcp_fd failed\n");
+ goto fail;
+ }
+ }
+
+ if (debug) fprintf(stderr, "httpget: writing output\n");
+ do_multiline= (A_arg && (max_headers != 0 || max_body != 0));
+ if (do_multiline)
+ {
+ fd= open("/dev/urandom", O_RDONLY);
+ read(fd, rndbuf, sizeof(rndbuf));
+ close(fd);
+ fprintf(out_file, "BEGINRESULT ");
+ for (i= 0; i<sizeof(rndbuf); i++)
+ fprintf(out_file, "%02x", (unsigned char)rndbuf[i]);
+ fprintf(out_file, " %s %ld\n", A_arg, (long)time(NULL));
+ }
+
+ if (debug) fprintf(stderr, "httpget: getting result\n");
+ if (!check_result(tcp_file, &http_result))
+ {
+ printf("check_result failed\n");
+ goto fail;
+ }
+ if (debug) fprintf(stderr, "httpget: getting reply headers \n");
+ if (!eat_headers(tcp_file, &chunked, &content_length, &headers_size,
+ out_file, max_headers))
+ {
+ printf("eat_headers failed\n");
+ goto fail;
+ }
+
+ no_body= (do_head || http_result == 204 || http_result == 304 ||
+ http_result/100 == 1);
+
+ if (max_headers != 0 && max_body != 0)
+ fprintf(out_file, "\n"); /* separate headers from body */
+
+ if (no_body)
+ {
+ /* This reply will not have a body even if there is a
+ * content-length line.
+ */
+ }
+ else if (chunked)
+ {
+ if (!copy_chunked(tcp_file, out_file, &content_length,
+ max_body))
+ {
+ printf("copy_chunked failed\n");
+ goto fail;
+ }
+ }
+ else
+ {
+ if (!copy_bytes(tcp_file, out_file, &content_length, max_body))
+ {
+ printf("copy_bytes failed\n");
+ goto fail;
+ }
+ }
+
+fail:
+ gettimeofday(&tv_end, NULL);
+
+ tv_end.tv_sec -= tv_start.tv_sec;
+ tv_end.tv_usec -= tv_start.tv_usec;
+ if (tv_end.tv_usec < 0)
+ {
+ tv_end.tv_usec += 1000000;
+ tv_end.tv_sec--;
+ }
+
+ if (do_multiline)
+ {
+ fprintf(out_file, "ENDRESULT ");
+ for (i= 0; i<sizeof(rndbuf); i++)
+ fprintf(out_file, "%02x", (unsigned char)rndbuf[i]);
+ fprintf(out_file, "\n");
+ }
+
+ if (A_arg && do_summary)
+ {
+ fprintf(out_file, "%s %ld ",
+ A_arg, (long)time(NULL));
+ }
+ if (do_summary)
+ {
+ const char *v, *cmd;
+
+ if (do_get)
+ cmd= "GET";
+ else if (do_head)
+ cmd= "HEAD";
+ else
+ cmd= "POST";
+ if (family == AF_INET)
+ v= "4";
+ else if (family == AF_INET6)
+ v= "6";
+ else
+ v= "?";
+
+ fprintf(out_file, "%s%s %s %d.%06d %03u %d %d\n",
+ cmd, v,
+ host_addr, (int)tv_end.tv_sec, (int)tv_end.tv_usec,
+ http_result, headers_size, content_length);
+ }
+
+ if (debug) fprintf(stderr, "httpget: done\n");
+
+ result= 0;
+
+leave:
+ if (fdH != -1) close(fdH);
+ if (fdF != -1) close(fdF);
+ if (fdS != -1) close(fdS);
+ if (fd != -1) close(fd);
+ if (tcp_file) fclose(tcp_file);
+ if (tcp_fd != -1) close(tcp_fd);
+ if (out_file && out_file_needs_closing) fclose(out_file);
+ if (host) free(host);
+ if (port) free(port);
+ if (hostport) free(hostport);
+ if (path) free(path);
+ if (filelist) free(filelist);
+
+ printf("clearing alarm\n");
+ alarm(0);
+ signal(SIGPIPE, SIG_DFL);
+
+ return result;
+
+err:
+ result= 1;
+ goto leave;
+}
+
+static int write_to_tcp_fd (int fd, FILE *tcp_file)
+{
+ int r;
+ /* Copy file */
+ while(r= read(fd, buffer, sizeof(buffer)), r > 0)
+ {
+ if (fwrite(buffer, r, 1, tcp_file) != 1)
+ {
+ report_err("error writing to tcp connection");
+ return 0;
+ }
+ }
+ if (r == -1)
+ fatal_err("error reading from file");
+ return 1;
+}
+
+
+static int parse_url(char *url, char **hostp, char **portp, char **hostportp,
+ char **pathp)
+{
+ char *item;
+ const char *cp, *np, *prefix;
+ size_t len;
+
+ *hostp= NULL;
+ *portp= NULL;
+ *hostportp= NULL;
+ *pathp= NULL;
+
+ /* the url must start with 'http://' */
+ prefix= "http://";
+ len= strlen(prefix);
+ if (strncasecmp(prefix, url, len) != 0)
+ {
+ report("bad prefix in url '%s'", url);
+ goto fail;
+ }
+
+ cp= url+len;
+
+ /* Get hostport part */
+ np= strchr(cp, '/');
+ if (np != NULL)
+ len= np-cp;
+ else
+ {
+ len= strlen(cp);
+ np= cp+len;
+ }
+ if (len == 0)
+ {
+ report("missing host part in url '%s'", url);
+ return 0;
+ }
+ item= malloc(len+1);
+ if (!item) fatal("out of memory");
+ memcpy(item, cp, len);
+ item[len]= '\0';
+ *hostportp= item;
+
+ /* The remainder is the path */
+ cp= np;
+ if (cp[0] == '\0')
+ cp= "/";
+ len= strlen(cp);
+ item= malloc(len+1);
+ if (!item) fatal("out of memory");
+ memcpy(item, cp, len);
+ item[len]= '\0';
+ *pathp= item;
+
+ /* Extract the host name from hostport */
+ cp= *hostportp;
+ np= cp;
+ if (cp[0] == '[')
+ {
+ /* IPv6 address literal */
+ np= strchr(cp, ']');
+ if (np == NULL || np == cp+1)
+ {
+ report("malformed IPv6 address literal in url '%s'",
+ url);
+ goto fail;
+ }
+ }
+
+ np= strchr(np, ':');
+ if (np != NULL)
+ len= np-cp;
+ else
+ {
+ len= strlen(cp);
+ np= cp+len;
+ }
+ if (len == 0)
+ {
+ report("missing host part in url '%s'", url);
+ goto fail;
+ }
+ item= malloc(len+1);
+ if (!item) fatal("out of memory");
+ if (cp[0] == '[')
+ {
+ /* Leave out the square brackets */
+ memcpy(item, cp+1, len-2);
+ item[len-2]= '\0';
+ }
+ else
+ {
+ memcpy(item, cp, len);
+ item[len]= '\0';
+ }
+ *hostp= item;
+
+ /* Port */
+ cp= np;
+ if (cp[0] == '\0')
+ cp= "80";
+ else
+ cp++;
+ len= strlen(cp);
+ item= malloc(len+1);
+ if (!item) fatal("out of memory");
+ memcpy(item, cp, len);
+ item[len]= '\0';
+ *portp= item;
+
+ return 1;
+
+fail:
+ if (*hostp)
+ {
+ free(*hostp);
+ *hostp= NULL;
+ }
+ if (*portp)
+ {
+ free(*portp);
+ *portp= NULL;
+ }
+ if (*hostportp)
+ {
+ free(*hostportp);
+ *hostportp= NULL;
+ }
+ if (*pathp)
+ {
+ free(*pathp);
+ *pathp= NULL;
+ }
+ return 0;
+}
+
+static int check_result(FILE *tcp_file, int *result)
+{
+ int major, minor;
+ size_t len;
+ char *cp, *check, *line;
+ const char *prefix;
+
+ *result= -1; /* Signal error actually getting a result */
+
+ if (fgets(buffer, sizeof(buffer), tcp_file) == NULL)
+ {
+ if (feof(tcp_file))
+ report("got unexpected EOF from server");
+ else
+ report_err("error reading from server");
+ return 0;
+ }
+
+ line= buffer;
+ cp= strchr(line, '\n');
+ if (cp == NULL)
+ {
+ report("line too long");
+ return 0;
+ }
+ cp[0]= '\0';
+ if (cp > line && cp[-1] == '\r')
+ cp[-1]= '\0';
+
+ /* Check http version */
+ prefix= "http/";
+ len= strlen(prefix);
+ if (strncasecmp(prefix, line, len) != 0)
+ {
+ report("bad prefix in response '%s'", line);
+ return 0;
+ }
+ cp= line+len;
+ major= strtoul(cp, &check, 10);
+ if (check == cp || check[0] != '.')
+ {
+ report("bad major version in response '%s'", line);
+ return 0;
+ }
+ cp= check+1;
+ minor= strtoul(cp, &check, 10);
+ if (check == cp || check[0] == '\0' ||
+ !isspace(*(unsigned char *)check))
+ {
+ report("bad major version in response '%s'", line);
+ return 0;
+ }
+
+ skip_spaces(check, &cp);
+
+ if (!isdigit(*(unsigned char *)cp))
+ {
+ report("bad status code in response '%s'", line);
+ return 0;
+ }
+ *result= strtoul(cp, NULL, 10);
+
+ return 1;
+}
+
+static int eat_headers(FILE *tcp_file, int *chunked, int *content_length,
+ int *headers_size, FILE *out_file, int max_headers)
+{
+ int tot_headers;
+ char *line, *cp, *ncp, *check;
+ size_t len;
+ const char *kw;
+
+ *chunked= 0;
+ *content_length= -1;
+ tot_headers= 0;
+ *headers_size= 0;
+ while (fgets(buffer, sizeof(buffer), tcp_file) != NULL)
+ {
+ line= buffer;
+ len=strlen(line);
+ cp= strchr(line, '\n');
+ if (cp == NULL)
+ {
+ report("line too long");
+ return 0;
+ }
+ cp[0]= '\0';
+ if (cp > line && cp[-1] == '\r')
+ cp[-1]= '\0';
+
+ if (line[0] == '\0')
+ return 1; /* End of headers */
+
+ *headers_size += len;
+
+ if (debug) fprintf(stderr, "httpget: got line '%s'\n", line);
+
+ len= strlen(line);
+ if (tot_headers+len+1 <= max_headers)
+ {
+ fprintf(out_file, "%s\n", line);
+ tot_headers += len+1;
+ } else if (tot_headers <= max_headers && max_headers != 0)
+ {
+ /* Fill up remaining space and report truncation */
+ if (tot_headers < max_headers)
+ {
+ fprintf(out_file, "%.*s\n", max_headers-tot_headers,
+ line);
+ }
+ fprintf(out_file, "[...]\n");
+
+ tot_headers += len+1;
+ }
+
+ if (time_tolerance && strncmp(line, "Date: ", 6) == 0)
+ {
+ /* Try to set time from server */
+ time_t now, tim, tolerance;
+ struct tm tm;
+
+ tolerance= strtoul(time_tolerance, &cp, 10);
+ if (cp[0] != '\0')
+ {
+ fatal("unable to parse tolerance '%s'",
+ time_tolerance);
+ }
+ cp= strptime(line+6, "%a, %d %b %Y %H:%M:%S ", &tm);
+ if (!cp || strcmp(cp, "GMT") != 0)
+ {
+ if (debug)
+ {
+ fprintf(stderr,
+ "unable to parse time '%s'\n",
+ line+6);
+ }
+ }
+ tim= timegm(&tm);
+ now= time(NULL);
+ if (now < tim-tolerance || now > tim+tolerance)
+ {
+ if (debug)
+ { fprintf(stderr,
+ "setting time, time difference is %d\n",
+ (int)(tim-now));
+ }
+ stime(&tim);
+ }
+ }
+
+
+ cp= line;
+ skip_spaces(cp, &ncp);
+ if (ncp != line)
+ continue; /* Continuation line */
+
+ cp= ncp;
+ while (ncp[0] != '\0' && ncp[0] != ':' &&
+ !isspace((unsigned char)ncp[0]))
+ {
+ ncp++;
+ }
+
+ kw= "Transfer-Encoding";
+ len= strlen(kw);
+ if (strncasecmp(cp, kw, len) == 0)
+ {
+ /* Skip optional white space */
+ cp= ncp;
+ skip_spaces(cp, &cp);
+
+ if (cp[0] != ':')
+ {
+ report("malformed content-length header", line);
+ return 0;
+ }
+ cp++;
+
+ /* Skip more white space */
+ skip_spaces(cp, &cp);
+
+ /* Should have the value by now */
+ kw= "chunked";
+ len= strlen(kw);
+ if (strncasecmp(cp, kw, len) != 0)
+ continue;
+ /* make sure we have end of line or white space */
+ if (cp[len] != '\0' && isspace((unsigned char)cp[len]))
+ continue;
+ *chunked= 1;
+ continue;
+ }
+
+ kw= "Content-length";
+ len= strlen(kw);
+ if (strncasecmp(cp, kw, len) != 0)
+ continue;
+
+ /* Skip optional white space */
+ cp= ncp;
+ skip_spaces(cp, &cp);
+
+ if (cp[0] != ':')
+ {
+ report("malformed content-length header", line);
+ return 0;
+ }
+ cp++;
+
+ /* Skip more white space */
+ skip_spaces(cp, &cp);
+
+ /* Should have the value by now */
+ *content_length= strtoul(cp, &check, 10);
+ if (check == cp)
+ {
+ report("malformed content-length header", line);
+ return 0;
+ }
+
+ /* And after that we should have just white space */
+ cp= check;
+ skip_spaces(cp, &cp);
+
+ if (cp[0] != '\0')
+ {
+ report("malformed content-length header", line);
+ return 0;
+ }
+ }
+ if (feof(tcp_file))
+ report("got unexpected EOF from server");
+ else
+ report_err("error reading from server");
+ return 0;
+}
+
+static int connect_to_name(char *host, char *port, int only_v4, int only_v6,
+ struct timeval *start_time, int *gerr)
+{
+ int r, s, s_errno;
+ struct addrinfo *res, *aip;
+ struct addrinfo hints;
+
+ if (debug) fprintf(stderr, "httpget: before getaddrinfo\n");
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_socktype= SOCK_STREAM;
+ if (only_v4)
+ hints.ai_family= AF_INET;
+ if (only_v6)
+ hints.ai_family= AF_INET6;
+ r= getaddrinfo(host, port, &hints, &res);
+ *gerr= r;
+ if (r != 0)
+ {
+ report("unable to resolve '%s': %s", host, gai_strerror(r));
+ return -1;
+ }
+
+ s_errno= 0;
+ s= -1;
+ for (aip= res; aip != NULL; aip= aip->ai_next)
+ {
+ family= res->ai_family;
+ getnameinfo(res->ai_addr, res->ai_addrlen, host_addr,
+ sizeof(host_addr), NULL, 0, NI_NUMERICHOST);
+
+ gettimeofday(start_time, NULL);
+ s= socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s == -1)
+ {
+ s_errno= errno;
+ continue;
+ }
+
+ if (debug) fprintf(stderr, "httpget: before connect\n");
+ if (connect(s, res->ai_addr, res->ai_addrlen) == 0)
+ break;
+
+ s_errno= errno;
+ close(s);
+ s= -1;
+ }
+
+ freeaddrinfo(res);
+ if (s == -1)
+ errno= s_errno;
+ return s;
+}
+
+char *do_dir(char *dir_name, off_t *lenp)
+{
+ size_t currsize, allocsize, dirlen, len;
+ char *list, *tmplist, *path;
+ DIR *dir;
+ struct dirent *de;
+ struct stat sb;
+
+ /* Scan a directory for files. Return the filenames asa list of
+ * strings. An empty string terminates the list. Also compute the
+ * total size of the files
+ */
+ *lenp= 0;
+ currsize= 0;
+ allocsize= 0;
+ list= NULL;
+ dir= opendir(dir_name);
+ if (dir == NULL)
+ {
+ report_err("opendir failed for '%s'", dir_name);
+ return NULL;
+ }
+
+ dirlen= strlen(dir_name);
+ while (de= readdir(dir), de != NULL)
+ {
+ /* Concat dir and entry */
+ len= dirlen + 1 + strlen(de->d_name) + 1;
+ if (currsize+len > allocsize)
+ {
+ allocsize += 4096;
+ tmplist= realloc(list, allocsize);
+ if (!tmplist)
+ {
+ free(list);
+ report("realloc failed for %d bytes",
+ allocsize);
+ closedir(dir);
+ return NULL;
+ }
+ list= tmplist;
+ }
+ path= list+currsize;
+
+ strlcpy(path, dir_name, allocsize-currsize);
+ strlcat(path, "/", allocsize-currsize);
+ strlcat(path, de->d_name, allocsize-currsize);
+
+ if (stat(path, &sb) != 0)
+ {
+ report_err("stat '%s' failed", path);
+ free(list);
+ closedir(dir);
+ return NULL;
+ }
+
+ if (!S_ISREG(sb.st_mode))
+ continue; /* Just skip entry */
+
+ currsize += len;
+ *lenp += sb.st_size;
+ }
+ closedir(dir);
+
+ /* Add empty string to terminate the list */
+ len= 1;
+ if (currsize+len > allocsize)
+ {
+ allocsize += 4096;
+ tmplist= realloc(list, allocsize);
+ if (!tmplist)
+ {
+ free(list);
+ report("realloc failed for %d bytes", allocsize);
+ return NULL;
+ }
+ list= tmplist;
+ }
+ path= list+currsize;
+
+ *path= '\0';
+
+ return list;
+}
+
+static int copy_chunked(FILE *in_file, FILE *out_file, int *length, int max_body)
+{
+ int need_nl;
+ size_t len, offset, size, tot_body;
+ char *cp, *line, *check;
+
+ *length= 0;
+ need_nl= 0;
+ tot_body= 0;
+ for (;;)
+ {
+ /* Get a chunk size */
+ if (fgets(buffer, sizeof(buffer), in_file) == NULL)
+ {
+ report("error reading input");
+ return 0;
+ }
+
+ line= buffer;
+ cp= strchr(line, '\n');
+ if (cp == NULL)
+ {
+ report("line too long");
+ return 0;
+ }
+ cp[0]= '\0';
+ if (cp > line && cp[-1] == '\r')
+ cp[-1]= '\0';
+
+ if (debug)
+ {
+ fprintf(stderr, "httpget: got chunk line '%s'\n", line);
+ }
+ len= strtoul(line, &check, 16);
+ if (check[0] != '\0' && !isspace(*(unsigned char *)check))
+ {
+ report("bad chunk line '%s'", line);
+ return 0;
+ }
+ if (!len)
+ break;
+
+ *length += len;
+
+ offset= 0;
+
+ while (offset < len)
+ {
+ size= len-offset;
+ if (size > sizeof(buffer))
+ size= sizeof(buffer);
+ if (fread(buffer, size, 1, in_file) != 1)
+ {
+ report("error reading input");
+ return 0;
+ }
+
+ if (tot_body+size <= max_body)
+ {
+ if (fwrite(buffer, size, 1, out_file) != 1)
+ fatal_err("error writing output");
+ need_nl= (buffer[size-1] != '\n');
+ tot_body += len;
+ } else if (tot_body <= max_body && max_body != 0)
+ {
+ /* Fill up remaining space and report truncation */
+ if (tot_body < max_body)
+ {
+ if (fwrite(buffer, max_body-tot_body, 1,
+ out_file) != 1)
+ {
+ fatal_err(
+ "error writing output");
+ }
+ }
+ fprintf(out_file, "\n[...]\n");
+ need_nl= 0;
+ tot_body += len;
+ }
+
+ offset += size;
+ }
+
+ /* Expect empty line after data */
+ if (fgets(buffer, sizeof(buffer), in_file) == NULL)
+ {
+ report("error reading input");
+ return 0;
+ }
+
+ line= buffer;
+ cp= strchr(line, '\n');
+ if (cp == NULL)
+ {
+ report("line too long");
+ return 0;
+ }
+ cp[0]= '\0';
+ if (cp > line && cp[-1] == '\r')
+ cp[-1]= '\0';
+ if (line[0] != '\0')
+ {
+ report("Garbage after chunk data");
+ return 0;
+ }
+ }
+
+ if (max_body && need_nl)
+ fprintf(out_file, "\n");
+
+ for (;;)
+ {
+ /* Get an end-of-chunk line */
+ if (fgets(buffer, sizeof(buffer), in_file) == NULL)
+ {
+ report("error reading input");
+ return 0;
+ }
+
+ line= buffer;
+ cp= strchr(line, '\n');
+ if (cp == NULL)
+ {
+ report("line too long");
+ return 0;
+ }
+ cp[0]= '\0';
+ if (cp > line && cp[-1] == '\r')
+ cp[-1]= '\0';
+ if (line[0] == '\0')
+ break;
+
+ if (debug)
+ {
+ fprintf(stderr,
+ "httpget: got end-of-chunk line '%s'\n", line);
+ }
+ }
+ return 1;
+}
+
+static int copy_bytes(FILE *in_file, FILE *out_file, int *length,
+ int max_body)
+{
+ int len, need_nl;
+ size_t offset, size;
+
+ offset= 0;
+
+ need_nl= 0;
+ len= *length;
+ while (len == -1 || offset < len)
+ {
+ if (len == -1)
+ {
+ size= sizeof(buffer);
+
+ size= fread(buffer, 1, sizeof(buffer), in_file);
+ if (size == 0)
+ {
+ if (feof(in_file))
+ break; /* Got EOF */
+ report_err("error reading input");
+
+ if (max_body && need_nl)
+ fprintf(out_file, "\n");
+ return 0;
+ }
+ }
+ else
+ {
+ size= len-offset;
+ if (size > sizeof(buffer))
+ size= sizeof(buffer);
+
+ if (fread(buffer, size, 1, in_file) != 1)
+ {
+ report_err("error reading input");
+
+ if (max_body && need_nl)
+ fprintf(out_file, "\n");
+ return 0;
+ }
+ }
+
+ if (offset+size <= max_body)
+ {
+ if (fwrite(buffer, size, 1, out_file) != 1)
+ fatal_err("error writing output");
+ need_nl= (buffer[size-1] != '\n');
+ }
+ else if (offset <= max_body && max_body != 0)
+ {
+ /* Fill up remaining space and report truncation */
+ if (offset < max_body)
+ {
+ if (fwrite(buffer, max_body-offset, 1,
+ out_file) != 1)
+ {
+ fatal_err("error writing output");
+ }
+ }
+ fprintf(out_file, "\n[...]\n");
+ need_nl= 0;
+ }
+
+ offset += size;
+ }
+
+ if (max_body && need_nl)
+ fprintf(out_file, "\n");
+ if (len == -1)
+ *length= offset;
+ return 1;
+}
+
+static void skip_spaces(const char *cp, char **ncp)
+{
+ const unsigned char *ucp;
+
+ ucp= (const unsigned char *)cp;
+ while (ucp[0] != '\0' && isspace(ucp[0]))
+ ucp++;
+ *ncp= (char *)ucp;
+}
+
+static void got_alarm(int sig __attribute__((unused)) )
+{
+ // printf("got alarm\n");
+ // printf("switching tcp_fd to nonblocking\n");
+ if (tcp_fd != -1)
+ fcntl(tcp_fd, F_SETFL, fcntl(tcp_fd, F_GETFL) | O_NONBLOCK);
+ //printf("setting alarm again\n");
+ alarm(1);
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+"Usage: httpget\n");
+ fprintf(stderr,
+" [--post-header <file-to-post>] [--post-file <file-to-post>]\n");
+ fprintf(stderr,
+" [--post-footer <file-to-post>] \n");
+
+ fprintf(stderr,
+" [--post-footer <file-to-post>] [-O <output-file>] <url>\n");
+ exit(1);
+}
+
+static void fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ fprintf(stderr, "httpget: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+
+ va_end(ap);
+
+ exit(1);
+}
+
+static void fatal_err(const char *fmt, ...)
+{
+ int s_errno;
+ va_list ap;
+
+ s_errno= errno;
+
+ va_start(ap, fmt);
+
+ fprintf(stderr, "httpget: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": %s\n", strerror(s_errno));
+
+ va_end(ap);
+
+ exit(1);
+}
+
+static void report(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ fprintf(stderr, "httpget: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+
+ va_end(ap);
+}
+
+static void report_err(const char *fmt, ...)
+{
+ int s_errno;
+ va_list ap;
+
+ s_errno= errno;
+
+ va_start(ap, fmt);
+
+ fprintf(stderr, "httpget: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": %s\n", strerror(s_errno));
+
+ va_end(ap);
+}