Touch up gopher support. Rewrite the main loop in main().
Update changes. Add README.
This commit is contained in:
parent
f27c613480
commit
f93a73148f
10
CHANGES
10
CHANGES
@ -1,4 +1,12 @@
|
||||
2021/11/29 - v0.1b
|
||||
2022/12/7 - v0.2b
|
||||
-It did segfault. Segfaults less now.
|
||||
-Still has TLS.
|
||||
-Still smaller and more readable than hurl.
|
||||
-Has Gopher now.
|
||||
-Maybe works on BSD.
|
||||
-Add an actual README.
|
||||
|
||||
2022/11/29 - v0.1b
|
||||
Features:
|
||||
-Doesn't segfault. Probably.
|
||||
-Has TLS.
|
||||
|
43
README
43
README
@ -0,0 +1,43 @@
|
||||
What is it?
|
||||
===========
|
||||
Terrible attempt at writing a smaller replacement for cURL. Will include
|
||||
a library in the future to make network operations in C convenient.
|
||||
|
||||
Why?
|
||||
====
|
||||
Because there weren't any HTTPS clients that were small, readable, and
|
||||
portable with the features I needed. Also I needed to learn sockets.
|
||||
|
||||
Okay, but you should still use cURL in your other projects.
|
||||
===========================================================
|
||||
No, I shouldn't. cURL is huge, doesn't build with smaller compilers, and
|
||||
has way too many features. Apportate is a cantrip, cURL is a level nine
|
||||
spell.
|
||||
|
||||
What's apportate's featureset, anyway?
|
||||
======================================
|
||||
Right now, it retrieves data over HTTP or Gopher, and can support TLS,
|
||||
at least when it comes to HTTPS. I'd like a more general approach to
|
||||
TLS support in the future.
|
||||
|
||||
Its source code is small (< 400 lines of C total according to cloc right
|
||||
now) and uses only POSIX routines -- there's no temptation to use GNU
|
||||
or BSD features because I develop against musl and tcc, and I don't even
|
||||
have the Linux manpages on any of my systems.
|
||||
|
||||
Furthermore, it has a relatively well-modularized design, at least for
|
||||
my current ability. It could be much better, if I had more experience,
|
||||
but it's still quite easy to add support for new protocols. For example,
|
||||
adding support for Gopher took me less than five minutes, and required
|
||||
no real edits to main aside from a kludge to avoid seeking for an HTTP
|
||||
response header terminator.
|
||||
|
||||
Apportate also aims to have actually useful diagnostics; that is,
|
||||
compared to other tools, apportate aims to only provide useful error
|
||||
output. In the case of success, it follows the Rule of Silence; on
|
||||
unrecoverable errors, it aborts immediately. It supports multiple levels
|
||||
of verbosity, and exposes almost all of its internals.
|
||||
|
||||
Its simple design and use should also make it relatively convenient
|
||||
for inclusion in shell scripts.
|
||||
|
@ -1 +1 @@
|
||||
int reqgen_goph(const char *path, char **nbuf);
|
||||
int reqgen_gopher(const char *path, char **nbuf);
|
||||
|
326
src/main.c
326
src/main.c
@ -4,6 +4,7 @@
|
||||
enum
|
||||
{
|
||||
/* display a status bar */
|
||||
/* currently not implemented */
|
||||
B,
|
||||
/* put the file somewhere other than the current directory */
|
||||
O,
|
||||
@ -12,11 +13,12 @@ enum
|
||||
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 */
|
||||
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
|
||||
};
|
||||
|
||||
@ -26,175 +28,189 @@ char param[4] = {0};
|
||||
char *outpath;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, translen, gotheader;
|
||||
int sockd;
|
||||
char recvbuf[BUFSIZ+1] = {0};
|
||||
char *sendbufp, *offsetp, *errstr;
|
||||
FILE *filed;
|
||||
struct tls *tlsc;
|
||||
uri uristruct;
|
||||
|
||||
i = translen = gotheader = sockd = 0;
|
||||
sendbufp = offsetp = 0;
|
||||
filed = 0;
|
||||
|
||||
|
||||
for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0)
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
case 'b':
|
||||
param[B] = 1;
|
||||
break;
|
||||
int i, translen, gotheader;
|
||||
int sockd;
|
||||
char *recvbuf;
|
||||
char *sendbufp, *offsetp, *errstr;
|
||||
FILE *filed;
|
||||
struct tls *tlsc;
|
||||
uri uristruct;
|
||||
|
||||
case 'o':
|
||||
if(!param[Q])
|
||||
{
|
||||
param[O] = 1;
|
||||
}
|
||||
outpath = optarg;
|
||||
break;
|
||||
i = translen = sockd = 0;
|
||||
sendbufp = offsetp = 0;
|
||||
if( (recvbuf = malloc(BUFSIZ+1)) == NULL)
|
||||
{
|
||||
errstr = "failed to init";
|
||||
goto err;
|
||||
}
|
||||
|
||||
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:
|
||||
errstr = "apport: uri";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
filed = 0;
|
||||
tlsc = NULL;
|
||||
|
||||
|
||||
/* we should probably modify uri_parse to return a zero value on failure... */
|
||||
if(uri_parse(argv[optind], &uristruct))
|
||||
{
|
||||
errstr = "invalid URI.";
|
||||
goto err;
|
||||
}
|
||||
for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0)
|
||||
{
|
||||
switch(i)
|
||||
{
|
||||
case 'b':
|
||||
param[B] = 1;
|
||||
break;
|
||||
|
||||
/* 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'. */
|
||||
if(outpath == NULL && !strlen((outpath = (1 + strrchr(uristruct.path, '/')))))
|
||||
{
|
||||
outpath = "default";
|
||||
}
|
||||
case 'o':
|
||||
param[O] = 1;
|
||||
outpath = optarg;
|
||||
break;
|
||||
|
||||
/* generate our request */
|
||||
if( !(sendbufp = reqgen(&uristruct)))
|
||||
{
|
||||
errstr = "Unknown protocol.";
|
||||
goto err;
|
||||
}
|
||||
case 'q':
|
||||
param[Q] = 1;
|
||||
param[B] = 0;
|
||||
param[V] = 0;
|
||||
break;
|
||||
|
||||
if(param[V])
|
||||
{
|
||||
fprintf(stderr, "request: %s\n", sendbufp);
|
||||
}
|
||||
case 'v':
|
||||
/* we handle v differently because we want to support different levels of verbosity */
|
||||
if(!param[Q])
|
||||
{
|
||||
param[V]++;
|
||||
}
|
||||
break;
|
||||
|
||||
/* having a routine or a global variable to track whether we need TLS would be */
|
||||
/* nice to add in the future */
|
||||
/* Note: at the moment, if the TLS pointer passed is non-NULL, */
|
||||
/* dial's return code can only be treated as an indicator of success. */
|
||||
|
||||
if(!strcmp(uristruct.proto, "https"))
|
||||
{
|
||||
if(! (sockd = dial(uristruct.fqdn, uristruct.proto, &tlsc)))
|
||||
{
|
||||
errstr = "Couldn't connect to host using the specified protocol.";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(! (sockd = dial(uristruct.fqdn, uristruct.proto, NULL)))
|
||||
{
|
||||
errstr = "Couldn't connect to host using the specified protocol.";
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if(! (filed = fopen(outpath, "w")))
|
||||
{
|
||||
errstr = "Couldn't open file.";
|
||||
goto err;
|
||||
}
|
||||
default:
|
||||
errstr = "[-vb] [-q] [-o <file>] uri";
|
||||
goto usage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!strcmp("https", uristruct.proto))
|
||||
{
|
||||
tls_write(tlsc, sendbufp, strlen(sendbufp));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(! (translen = send(sockd, sendbufp, strlen(sendbufp), 0)))
|
||||
{
|
||||
errstr = "Couldn't transmit request.";
|
||||
goto err;
|
||||
}
|
||||
else if(param[V])
|
||||
{
|
||||
fprintf(stderr, "sent: %d bytes\n", translen);
|
||||
}
|
||||
}
|
||||
/* we should probably modify uri_parse to return a zero value on failure... */
|
||||
if(uri_parse(argv[optind], &uristruct))
|
||||
{
|
||||
errstr = "[-q|[-v -b]] [-o <file>] uri";
|
||||
goto usage;
|
||||
}
|
||||
|
||||
for(gotheader = 0, translen = 1; translen; memset(recvbuf, 0, BUFSIZ+1))
|
||||
{
|
||||
/* using an abstraction over tls_read/write and send/recv would eliminate */
|
||||
/* a lot of redundant code here. */
|
||||
if(!strcmp("https", uristruct.proto))
|
||||
{
|
||||
translen = tls_read(tlsc, recvbuf, BUFSIZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
translen = recv(sockd, recvbuf, BUFSIZ, 0);
|
||||
}
|
||||
/* 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'. */
|
||||
if(NULL == outpath && !strlen((outpath = (1 + strrchr(uristruct.path, '/')))))
|
||||
{
|
||||
outpath = "default";
|
||||
}
|
||||
|
||||
if(!translen)
|
||||
{
|
||||
break;
|
||||
}
|
||||
/* generate our request */
|
||||
if( !(sendbufp = reqgen(&uristruct)))
|
||||
{
|
||||
errstr = "Unknown protocol.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(param[V])
|
||||
{
|
||||
printf("recv: %d bytes\n", translen);
|
||||
}
|
||||
|
||||
if(!gotheader)
|
||||
{
|
||||
offsetp = (strstr(recvbuf, "\r\n\r\n") + 4);
|
||||
i = (int) (offsetp - recvbuf);
|
||||
gotheader = 1;
|
||||
}
|
||||
{
|
||||
fprintf(stderr, "request: %s\n", sendbufp);
|
||||
}
|
||||
|
||||
/* having a routine or a global variable to track whether we need TLS would be */
|
||||
/* nice to add in the future */
|
||||
/* could probably store whether tls is necessary in a char within uristruct... */
|
||||
/* Note: at the moment, if the TLS pointer passed is non-NULL, */
|
||||
/* dial's return code can only be treated as an indicator of success. */
|
||||
|
||||
if(!strcmp("https", uristruct.proto))
|
||||
{
|
||||
sockd = dial(uristruct.fqdn, uristruct.proto, &tlsc);
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetp = recvbuf;
|
||||
i = 0;
|
||||
}
|
||||
{
|
||||
sockd = dial(uristruct.fqdn, uristruct.proto, NULL);
|
||||
}
|
||||
|
||||
if(!sockd)
|
||||
{
|
||||
errstr = "Couldn't connect to the host using the specified protocol.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(tlsc)
|
||||
{
|
||||
translen = tls_write(tlsc, sendbufp, strlen(sendbufp));
|
||||
}
|
||||
else
|
||||
{
|
||||
translen = send(sockd, sendbufp, strlen(sendbufp), 0);
|
||||
}
|
||||
|
||||
if(translen)
|
||||
{
|
||||
if(param[V])
|
||||
{
|
||||
printf("send: %d bytes\n", translen);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errstr = "Couldn't transmit data.";
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* now for a slightly more complex version of the same routine: until we've encountered the */
|
||||
/* delimiter, "\r\n\r\n", don't write to disk. Once we have, calculate the size of the body, */
|
||||
/* then write that number of bytes to disk starting from the offset. Then, write everything */
|
||||
/* until end of transmission. */
|
||||
for(gotheader = 0, offsetp = NULL, translen = 1; translen > 0; memset(recvbuf, 0, BUFSIZ+1))
|
||||
{
|
||||
if( NULL == tlsc)
|
||||
{
|
||||
printf("recv: %d bytes\n", translen = recv(sockd, recvbuf, BUFSIZ, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("recv: %d bytes\n", translen = tls_read(tlsc, recvbuf, BUFSIZ));
|
||||
}
|
||||
|
||||
/* if we haven't gotten the header and our delimiter isn't in the */
|
||||
/* received string, we're getting a multipart header and need to */
|
||||
/* skip over it. */
|
||||
/* right now we assume HTTP/S -- this is undesirable... */
|
||||
if( strcmp("gopher", uristruct.proto) && !gotheader)
|
||||
{
|
||||
if( NULL == (offsetp = strstr(recvbuf, "\r\n\r\n")))
|
||||
{
|
||||
printf("continuing...\n");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
gotheader = 1;
|
||||
offsetp += 4;
|
||||
printf("got the header\n");
|
||||
}
|
||||
}
|
||||
|
||||
if( !filed && !(filed = fopen(outpath, "w")))
|
||||
{
|
||||
errstr = "couldn't open file";
|
||||
goto err;
|
||||
}
|
||||
|
||||
fwrite(offsetp, sizeof(char), (translen - (offsetp - recvbuf)), filed);
|
||||
offsetp = recvbuf;
|
||||
}
|
||||
free(recvbuf);
|
||||
close(sockd);
|
||||
fclose(filed);
|
||||
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
|
||||
err:
|
||||
fprintf(stderr, "%s: %s\n", argv[0], errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
usage:
|
||||
fprintf(stderr, "usage: %s: %s\n", argv[0], errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
fwrite(offsetp, sizeof(char), translen-i, filed);
|
||||
}
|
||||
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
|
||||
err:
|
||||
fprintf(stderr, "%s: %s\n", argv[0], errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user