diff --git a/CHANGES b/CHANGES index 3b9f21f..5fd601b 100644 --- a/CHANGES +++ b/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. diff --git a/README b/README index e69de29..ae3feca 100644 --- a/README +++ b/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. + diff --git a/src/gopher.h b/src/gopher.h index f30bd7e..6d2f346 100644 --- a/src/gopher.h +++ b/src/gopher.h @@ -1 +1 @@ -int reqgen_goph(const char *path, char **nbuf); +int reqgen_gopher(const char *path, char **nbuf); diff --git a/src/main.c b/src/main.c index 2140731..e6c0220 100644 --- a/src/main.c +++ b/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 ] 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 ] 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); - - }