#include "headers.h" enum { /* display a status bar */ /* currently not implemented */ B, /* put the file somewhere other than the current directory */ O, /* silence all output -- the default */ /* note that if -q is passed explicitly, it will irrevocably override -v and -b */ Q, /* increase the verbosity level. higher levels imply you want the content lower levels would provide */ /* all verbose output is sent to stderr for ease of capture */ /* 0: no informational, statistical or debug output */ /* 1: informational output (Sending request FOO to host BAR, Received response header BAZ, etc) */ /* 2: statistical. informational+size of header and body in bytes, round-trip time, etc */ /* 3+: debug. statistical+all internal state changes */ /* currently we only implement 1 */ V }; enum { /* change this to alter how many redirect attempts should be tried */ /* todo: make this a command-line parameter? */ REDIR_LIM = 10 }; char param[4] = {0}; /* below is to accompany param[O] */ char *outpath; int main(int argc, char **argv) { int i, translen, redirnum, gotheader; int sockd; char *recvbufp; char *sendbufp, *offsetp, *errstr; char *urip; FILE *filed; struct tls *tlsc; uri uristruct; i = translen = sockd = 0; sendbufp = offsetp = 0; if( (recvbufp = malloc(BUFSIZ+1)) == NULL) { errstr = "failed to init"; goto err; } filed = 0; tlsc = NULL; if(argc == 1) { goto usage; } for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0) { switch(i) { case 'b': param[B] = 1; break; case 'o': param[O] = 1; outpath = optarg; break; case 'q': param[Q] = 1; param[B] = 0; param[V] = 0; break; case 'v': /* we handle v differently because we want to support different levels of verbosity */ if(!param[Q]) { param[V]++; } break; default: goto usage; } } urip = argv[optind]; for(redirnum = 0; redirnum < REDIR_LIM;) { start: if(NULL != tlsc) { tls_free(tlsc); tlsc = NULL; } /* I don't care *what* you say, the system should *never* in a sane context return 0, 1, or 2 as a valid file descriptor */ /* libc reserves these */ if(2 < sockd) { close(sockd); sockd = 0; } if(filed) { fclose(filed); filed = NULL; } if(sendbufp) { free(sendbufp); sendbufp = NULL; } /* todo: init uristruct as well */ uristruct.proto = NULL; uristruct.fqdn = NULL; uristruct.path = NULL; if( uri_parse(urip, &uristruct)) { errstr = "couldn't parse URI."; goto err; } if(param[V] >= 2) { fprintf(stderr, "URI parsed, results follow...\nProtocol: %s\nFQDN: %s\nPath: %s\n", uristruct.proto, uristruct.fqdn, uristruct.path); if(param[V] >= 3) { fprintf(stderr, "length of proto: %lu\nlength of fqdn: %lu\nlength of path: %lu\n", strlen(uristruct.proto), strlen(uristruct.fqdn), strlen(uristruct.path)); } } sendbufp = reqgen(&uristruct); /* we should probably modify uri_parse to return a zero value on failure... */ if( NULL == (sendbufp = reqgen(&uristruct))) { fprintf(stderr, "couldn't generate request. Unknown protocol: %s?\n", uristruct.proto); exit(-1); } /* if outpath isn't set because we haven't received a -o param, */ /* then we should set the outpath to the final component of the */ /* URI. */ /* If we can't do that, we should just set it to 'default'. */ /* todo: maybe add a check to ensure we don't overwrite? but if */ /* the user tells us to, who are we to question them? */ if(NULL == outpath && !strlen((outpath = (1 + strrchr(uristruct.path, '/'))))) { outpath = "default"; } if(param[V]) { fprintf(stderr, "connecting to %s using protocol %s...\n", uristruct.fqdn, uristruct.proto); } /* once again, I'll repeat myself -- while a system *COULD* return 0, 1, or 2, it *SHOULD NEVER DO SO* in a sane environment */ if( 2 >= (sockd = dial(uristruct.fqdn, uristruct.proto))) { errstr = "failed to connect"; goto err; } /* todo: upgrade this to a more general mechanism */ if(!strncmp("https", uristruct.proto, 5)) { if(NULL != tlsc) { tls_reset(tlsc); } struct tls_config *config = tls_config_new(); tlsc = tls_client(); tls_configure(tlsc, config); if(-1 == tls_connect_socket(tlsc, sockd, uristruct.fqdn)) { errstr = "failed to upgrade connection to use TLS, aborting\n"; goto err; } } if(param[V] >= 2) { fprintf(stderr, "Sending request...\n-----REQUEST START-----\n%s\n-----REQUEST END-----\n", sendbufp); } if(NULL != tlsc) { if(param[V]) { fprintf(stderr, "writing over tls...\n"); } if( -1 == (i = tls_write(tlsc, sendbufp, strlen(sendbufp)))) { fprintf(stderr, "libtls internal error: "); errstr = (char *) tls_error(tlsc); goto err; } } else { if(param[V]) { fprintf(stderr, "writing over socket...\n"); } i = send(sockd, sendbufp, strlen(sendbufp), 0); } if(param[V] >= 3) { fprintf(stderr, "sent: %d bytes\n", i); } /* actual read loop */ for(gotheader = 0, translen = 1; translen; memset(recvbufp, 0, BUFSIZ+1)) { if(NULL == tlsc) { translen = recv(sockd, recvbufp, BUFSIZ, 0); } else { translen = tls_read(tlsc, recvbufp, BUFSIZ); } if(param[V] >= 3) { fprintf(stderr, "recv: %d bytes\n", translen); } /* parsing here? */ if(!strncmp(uristruct.proto, "http", 4) && !gotheader) { switch(resp_parse_http(recvbufp)) { case -1: if(param[V]) { fprintf(stderr, "Response header parsing unnecessary, moving on...\n"); } break; case 200: /* by now we have the first transmission from the server that we actually care about */ /* we just need to get the the end of the headres, now that we're done with 'em */ if(param[V] >= 3) { fprintf(stderr, "200 OKAY, moving to end of header...\n"); } for(; NULL == (offsetp = strstr(recvbufp, "\r\n\r\n"));) { if(param[V] >= 2) { fprintf(stderr, "Searching to end of header...\n"); } if(NULL != tlsc) { tls_read(tlsc, recvbufp, BUFSIZ); } else { recv(sockd, recvbufp, BUFSIZ, 0); } } /* move forward four to get past the delimiter */ /* todo: add error checking in the case of never receiving a delimiter */ offsetp += 4; gotheader = 1; break; /* intentional drop through from 301 to 302 */ case 301: case 302: urip = http_get_keyval("Location", recvbufp); if(param[V]) { fprintf(stderr, "Redirecting to %s...\n", urip); } redirnum++; /* could use continue, but structured programming makes it easier to use goto in this circumstance... */ goto start; case 400: errstr = "400 Bad Request. Internal apport error?"; goto err; default: fprintf(stdout, "%s", recvbufp); errstr = "invalid response from server."; goto err; } } if( !filed && NULL == (filed = fopen(outpath, "w"))) { errstr = "couldn't open file."; goto err; } i = fwrite(offsetp, sizeof(char), translen - (offsetp - recvbufp), filed); if(param[V] >= 3) { fprintf(stderr, "fwrite: %d bytes\n", i); } offsetp = recvbufp; } tls_free(tlsc); close(sockd); fclose(filed); free(sendbufp); free(recvbufp); break; } exit(EXIT_SUCCESS); usage: fprintf(stderr, "usage: %s [-qvb] [-o file] uri\n", argv[0]); exit(EXIT_FAILURE); err: fprintf(stderr, "%s: %s\n", argv[0], errstr); exit(EXIT_FAILURE); }