/* -*- mode: C; c-basic-offset: 8; indent-tabs-mode: nil; tab-width: 8 -*- */ #include #include #include #include #include #include #include #include "url.h" #include "http.h" #include "options.h" #include "util.h" #include "llist.h" extern int default_opts; int redirect_count = 0; #define REDIRECT_MAX 10 #define USER_AGENT "snarf/" VERSION " (http://www.xach.com/snarf)" #define MOZILLA_USER_AGENT "Mozilla/4.0 (X11; Unix; Hi-mom)" #define MSIE_USER_AGENT "Mozilla/4.0 (Compatible; MSIE 4.0)" typedef struct _HttpHeader HttpHeader; typedef struct _HttpHeaderEntry HttpHeaderEntry; struct _HttpHeader { List *header_list; }; struct _HttpHeaderEntry { char *key; char *value; }; static HttpHeader * make_http_header(char *r) { HttpHeader *h = NULL; HttpHeaderEntry *he = NULL; char *s = NULL; char *raw_header = NULL; char *raw_header_head = NULL; raw_header = strdup(r); /* Track this to free at the end */ raw_header_head = raw_header; h = malloc(sizeof(HttpHeader)); h->header_list = list_new(); /* Skip the first line: "HTTP/1.X NNN Comment\r?\n" */ s = raw_header; while (*s != '\0' && *s != '\r' && *s != '\n') { s++; } while (*s != '\0' && isspace(*s)) { s++; } raw_header = s; s = strstr(raw_header, ": "); while (s) { /* Set ':' to '\0' to terminate the key */ *s++ = '\0'; he = malloc(sizeof(HttpHeaderEntry)); he->key = strdup(raw_header); /* Make it lowercase so we can lookup case-insensitive */ string_lowercase(he->key); /* Now get the value */ s++; raw_header = s; while (*s != '\0' && *s != '\r' && *s != '\n') { s++; } *s++ = '\0'; he->value = strdup(raw_header); list_append(h->header_list, he); /* Go to the next line */ while (*s != '\0' && isspace(*s)) { s++; } raw_header = s; s = strstr(raw_header, ": "); } free(raw_header_head); return h; } static void free_http_header(HttpHeader *h) { List *l; List *l1; HttpHeaderEntry *he; if (h == NULL) { return; } l = h->header_list; while (l && l->data) { he = l->data; free(he->key); free(he->value); free(l->data); l = l->next; } l = h->header_list; while (l) { l1 = l->next; free(l); l = l1; } } static char * get_header_value(char *key, HttpHeader *header) { char *value = NULL; char *retval = NULL; List *l = NULL; HttpHeaderEntry *he = NULL; l = header->header_list; while (l && l->data) { he = l->data; if (strcmp(he->key, key) == 0) { return strdup(he->value); } l = l->next; } return NULL; } static char * get_raw_header(int fd) { char *header = NULL; char buf[BUFSIZE]; /* this whole function is pathetic. please rewrite it for me. */ int bytes_read = 0; int total_read = 0; header = strdup(""); buf[0] = buf[1] = buf[2] = '\0'; while( (bytes_read = read(fd, buf, 1)) ) { total_read += bytes_read; header = strconcat(header, buf, NULL); if( total_read > 1) { if( strcmp(header + (total_read - 2), "\n\n") == 0 ) break; } if( total_read > 3 ) { if( strcmp(header + (total_read - 4), "\r\n\r\n") == 0 ) break; } } return header; } static char * get_request(UrlResource *rsrc) { char *request = NULL; char *auth = NULL; char buf[BUFSIZE]; Url *u; off_t file_size; u = rsrc->url; request = strconcat("GET ", u->path, u->file, " HTTP/1.0\r\n", "Host: ", u->host, "\r\n", NULL); if( u->username && u->password ) { auth = strconcat( u->username, ":", u->password, NULL); auth = base64(auth, strlen(auth)); request = strconcat(request, "Authorization: Basic ", auth, "\r\n", NULL); } if( rsrc->proxy_username && rsrc->proxy_password ) { auth = strconcat( rsrc->proxy_username, ":", rsrc->proxy_password, NULL); auth = base64(auth, strlen(auth)); request = strconcat(request, "Proxy-Authorization: Basic ", auth, "\r\n", NULL); } if( (rsrc->options & OPT_RESUME) && (file_size = get_file_size(rsrc->outfile)) ) { sprintf(buf, "%ld-", (long int )file_size); request = strconcat(request, "Range: bytes=", buf, "\r\n", NULL); } /* Use user's SNARF_HTTP_USER_AGENT env. var if present, as they might want to spoof some discriminant sites. (Or just increase the hit count for their favorite browser.) Alternately, use Mozilla or MSIE's User-Agent strings based on options set. */ request = strconcat(request, "User-Agent: ", NULL); if (getenv("SNARF_HTTP_USER_AGENT")) { request = strconcat(request, getenv("SNARF_HTTP_USER_AGENT"), NULL); } else if (rsrc->options & OPT_BE_MOZILLA) { request = strconcat(request, MOZILLA_USER_AGENT, NULL); } else if (rsrc->options & OPT_BE_MSIE) { request = strconcat(request, MSIE_USER_AGENT, NULL); } else { /* let snarf be snarf :-) */ request = strconcat(request, USER_AGENT, NULL); } /* This CRLF pair closes the User-Agent key-value set. */ request = strconcat(request, "\r\n", NULL); /* If SNARF_HTTP_REFERER is set, spoof it. */ if (getenv("SNARF_HTTP_REFERER")) { request = strconcat(request, "Referer: ", getenv("SNARF_HTTP_REFERER"), "\r\n", NULL); } request = strconcat(request, "\r\n", NULL); return request; } int http_transfer(UrlResource *rsrc) { FILE *out = NULL; Url *u = NULL; Url *proxy_url = NULL; Url *redir_u = NULL; char *request = NULL; char *raw_header = NULL; HttpHeader *header = NULL; char *len_string = NULL; char *new_location = NULL; char buf[BUFSIZE]; int sock = 0; ssize_t bytes_read = 0; int retval = 0; int i; /* make sure we haven't recursed too much */ if( redirect_count > REDIRECT_MAX ) { report(ERR, "redirection max count exceeded " "(looping redirect?)"); redirect_count = 0; return 0; } /* make sure everything's initialized to something useful */ u = rsrc->url; if( ! *(u->host) ) { report(ERR, "no host specified"); return 0; } /* fill in proxyness */ if( !rsrc->proxy ) { rsrc->proxy = get_proxy("HTTP_PROXY"); } if( !rsrc->outfile ) { if( u->file ) rsrc->outfile = strdup(u->file); else rsrc->outfile = strdup("index.html"); } if( !u->path ) u->path = strdup("/"); if( !u->file ) u->file = strdup(""); /* funny looking */ if( !u->port ) u->port = 80; rsrc->options |= default_opts; /* send the request to either the proxy or the remote host */ if( rsrc->proxy ) { proxy_url = url_new(); url_init(proxy_url, rsrc->proxy); if( !proxy_url->port ) proxy_url->port = 80; if( !proxy_url->host ) { report(ERR, "bad proxy `%s'", rsrc->proxy); return 0; } if( proxy_url->username ) rsrc->proxy_username = strdup(proxy_url->username); if( proxy_url->password ) rsrc->proxy_password = strdup(proxy_url->password); /* Prompt for proxy password if not specified */ if( proxy_url->username && !proxy_url->password ) { char *prompt = NULL; prompt = strconcat("Password for proxy ", proxy_url->username, "@", proxy_url->host, ": ", NULL); proxy_url->password = strdup(getpass(prompt)); free(prompt); } if( ! (sock = tcp_connect(proxy_url->host, proxy_url->port)) ) return 0; u->path = strdup(""); u->file = strdup(u->full_url); request = get_request(rsrc); write(sock, request, strlen(request)); } else /* no proxy */ { if( ! (sock = tcp_connect(u->host, u->port)) ) return 0; request = get_request(rsrc); write(sock, request, strlen(request)); } out = open_outfile(rsrc); if( !out ) { report(ERR, "opening %s: %s", rsrc->outfile, strerror(errno)); return 0; } /* check to see if it returned a HTTP 1.x response */ memset(buf, '\0', 5); bytes_read = read(sock, buf, 8); if( bytes_read == 0 ) { close(sock); return 0; } if( ! (buf[0] == 'H' && buf[1] == 'T' && buf[2] == 'T' && buf[3] == 'P') ) { if ((rsrc->options & OPT_RESUME) && rsrc->outfile_offset) { report(WARN, "server does not support resume, " "try again" " with -n (no resume)"); retval = 0; goto cleanup; } write(fileno(out), buf, bytes_read); } else { /* skip the header */ buf[bytes_read] = '\0'; raw_header = get_raw_header(sock); raw_header = strconcat(buf, raw_header, NULL); header = make_http_header(raw_header); if (rsrc->options & OPT_VERBOSE) { fwrite(raw_header, 1, strlen(raw_header), stderr); } /* check for redirects */ new_location = get_header_value("location", header); if (raw_header[9] == '3' && new_location ) { redir_u = url_new(); /* make sure we still send user/password along */ redir_u->username = safe_strdup(u->username); redir_u->password = safe_strdup(u->password); url_init(redir_u, new_location); rsrc->url = redir_u; redirect_count++; retval = transfer(rsrc); goto cleanup; } if (raw_header[9] == '4' || raw_header[9] == '5') { for(i = 0; raw_header[i] && raw_header[i] != '\n'; i++); raw_header[i] = '\0'; report(ERR, "HTTP error from server: %s", raw_header); retval = 0; goto cleanup; } len_string = get_header_value("content-length", header); if (len_string) rsrc->outfile_size = (off_t )atoi(len_string); if (get_header_value("content-range", header)) rsrc->outfile_size += rsrc->outfile_offset; if( (!rsrc->outfile_size) && (rsrc->options & OPT_RESUME) && !(rsrc->options & OPT_NORESUME) && rsrc->outfile_offset ) { report(WARN, "unable to determine remote file size, try again" " with -n (no resume)"); retval = 0; goto cleanup; } } if( ! dump_data(rsrc, sock, out) ) retval = 0; else retval = 1; cleanup: free_http_header(header); close(sock); fclose(out); return retval; }