367 lines
8.0 KiB
C
367 lines
8.0 KiB
C
#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,
|
|
|
|
/* set redir limit (not currently implemented) */
|
|
R
|
|
};
|
|
|
|
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);
|
|
}
|
|
|