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:
|
Features:
|
||||||
-Doesn't segfault. Probably.
|
-Doesn't segfault. Probably.
|
||||||
-Has TLS.
|
-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
|
enum
|
||||||
{
|
{
|
||||||
/* display a status bar */
|
/* display a status bar */
|
||||||
|
/* currently not implemented */
|
||||||
B,
|
B,
|
||||||
/* put the file somewhere other than the current directory */
|
/* put the file somewhere other than the current directory */
|
||||||
O,
|
O,
|
||||||
@ -12,11 +13,12 @@ enum
|
|||||||
Q,
|
Q,
|
||||||
/* increase the verbosity level. higher levels imply you want the content lower levels would provide */
|
/* increase the verbosity level. higher levels imply you want the content lower levels would provide */
|
||||||
/* all verbose output
|
/* 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 */
|
/* 0: no informational, statistical or debug output */
|
||||||
/* 1: informational output (Sending request FOO to host BAR, Received response header BAZ, etc) */
|
/* 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 */
|
/* 2: statistical. informational+size of header and body in bytes, round-trip time, etc */
|
||||||
/* 3+: debug. statistical+all internal state changes */
|
/* 3+: debug. statistical+all internal state changes */
|
||||||
|
/* currently we only implement 1 */
|
||||||
V
|
V
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,175 +28,189 @@ char param[4] = {0};
|
|||||||
char *outpath;
|
char *outpath;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
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)
|
int i, translen, gotheader;
|
||||||
{
|
int sockd;
|
||||||
case 'b':
|
char *recvbuf;
|
||||||
param[B] = 1;
|
char *sendbufp, *offsetp, *errstr;
|
||||||
break;
|
FILE *filed;
|
||||||
|
struct tls *tlsc;
|
||||||
|
uri uristruct;
|
||||||
|
|
||||||
case 'o':
|
i = translen = sockd = 0;
|
||||||
if(!param[Q])
|
sendbufp = offsetp = 0;
|
||||||
{
|
if( (recvbuf = malloc(BUFSIZ+1)) == NULL)
|
||||||
param[O] = 1;
|
{
|
||||||
}
|
errstr = "failed to init";
|
||||||
outpath = optarg;
|
goto err;
|
||||||
break;
|
}
|
||||||
|
|
||||||
case 'q':
|
filed = 0;
|
||||||
param[Q] = 1;
|
tlsc = NULL;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* we should probably modify uri_parse to return a zero value on failure... */
|
for(i = 0; (i = getopt(argc, argv, "bo:qv")) != -1; i = 0)
|
||||||
if(uri_parse(argv[optind], &uristruct))
|
{
|
||||||
{
|
switch(i)
|
||||||
errstr = "invalid URI.";
|
{
|
||||||
goto err;
|
case 'b':
|
||||||
}
|
param[B] = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
/* if outpath isn't set because we haven't received a -o param, */
|
case 'o':
|
||||||
/* then we should set the outpath to the final component of the */
|
param[O] = 1;
|
||||||
/* URI. */
|
outpath = optarg;
|
||||||
/* If we can't do that, we should just set it to 'default'. */
|
break;
|
||||||
if(outpath == NULL && !strlen((outpath = (1 + strrchr(uristruct.path, '/')))))
|
|
||||||
{
|
|
||||||
outpath = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* generate our request */
|
case 'q':
|
||||||
if( !(sendbufp = reqgen(&uristruct)))
|
param[Q] = 1;
|
||||||
{
|
param[B] = 0;
|
||||||
errstr = "Unknown protocol.";
|
param[V] = 0;
|
||||||
goto err;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if(param[V])
|
case 'v':
|
||||||
{
|
/* we handle v differently because we want to support different levels of verbosity */
|
||||||
fprintf(stderr, "request: %s\n", sendbufp);
|
if(!param[Q])
|
||||||
}
|
{
|
||||||
|
param[V]++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
/* having a routine or a global variable to track whether we need TLS would be */
|
default:
|
||||||
/* nice to add in the future */
|
errstr = "[-vb] [-q] [-o <file>] uri";
|
||||||
/* Note: at the moment, if the TLS pointer passed is non-NULL, */
|
goto usage;
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(!strcmp("https", uristruct.proto))
|
/* we should probably modify uri_parse to return a zero value on failure... */
|
||||||
{
|
if(uri_parse(argv[optind], &uristruct))
|
||||||
tls_write(tlsc, sendbufp, strlen(sendbufp));
|
{
|
||||||
}
|
errstr = "[-q|[-v -b]] [-o <file>] uri";
|
||||||
else
|
goto usage;
|
||||||
{
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(gotheader = 0, translen = 1; translen; memset(recvbuf, 0, BUFSIZ+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 */
|
||||||
/* using an abstraction over tls_read/write and send/recv would eliminate */
|
/* URI. */
|
||||||
/* a lot of redundant code here. */
|
/* If we can't do that, we should just set it to 'default'. */
|
||||||
if(!strcmp("https", uristruct.proto))
|
if(NULL == outpath && !strlen((outpath = (1 + strrchr(uristruct.path, '/')))))
|
||||||
{
|
{
|
||||||
translen = tls_read(tlsc, recvbuf, BUFSIZ);
|
outpath = "default";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
translen = recv(sockd, recvbuf, BUFSIZ, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!translen)
|
/* generate our request */
|
||||||
{
|
if( !(sendbufp = reqgen(&uristruct)))
|
||||||
break;
|
{
|
||||||
}
|
errstr = "Unknown protocol.";
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
if(param[V])
|
if(param[V])
|
||||||
{
|
{
|
||||||
printf("recv: %d bytes\n", translen);
|
fprintf(stderr, "request: %s\n", sendbufp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!gotheader)
|
/* having a routine or a global variable to track whether we need TLS would be */
|
||||||
{
|
/* nice to add in the future */
|
||||||
offsetp = (strstr(recvbuf, "\r\n\r\n") + 4);
|
/* could probably store whether tls is necessary in a char within uristruct... */
|
||||||
i = (int) (offsetp - recvbuf);
|
/* Note: at the moment, if the TLS pointer passed is non-NULL, */
|
||||||
gotheader = 1;
|
/* 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
|
else
|
||||||
{
|
{
|
||||||
offsetp = recvbuf;
|
sockd = dial(uristruct.fqdn, uristruct.proto, NULL);
|
||||||
i = 0;
|
}
|
||||||
}
|
|
||||||
|
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…
x
Reference in New Issue
Block a user