httpc.c (3868B)
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+httpc@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- #include <limits.h> // PAGESIZE
- #include <tls.h>
- #include <errno.h>
- #include <stdio.h> // fprintf
- #include <unistd.h> // write, fsync
- #include <string.h> // strlen, strerror
- #include <stdlib.h> // abort
- #include <stdbool.h>
- #include <fcntl.h> // open, O_*
- #include "httpc.h"
- int body = 0;
- bool verbose = false;
- static size_t
- read_header(const char *res, ssize_t len)
- {
- size_t header_len = 0;
- for(ssize_t i = 0; i<len; i++)
- {
- if(res[i] == '\r' && res[i+1] == '\n') i++;
- if(res[i] == '\n')
- {
- if(header_len == 0)
- {
- body = 1;
- return i+1;
- }
- if(verbose)
- {
- fputs("> ", stderr);
- fwrite(res, 1, header_len, stderr);
- fputs("\n", stderr);
- }
- return i+1;
- }
- header_len++;
- }
- // Maybe case where first packet didn't contain all headers should be handled
- // But who the heck even sends >PAGESIZE of headers
- abort();
- }
- int
- main(int argc, char *argv[])
- {
- int out_flags = O_WRONLY | O_CLOEXEC | O_CREAT | O_NOCTTY | O_TRUNC;
- char *out_name = NULL;
- int out = -1;
- int c = -1;
- while((c = getopt(argc, argv, ":o:v")) != -1)
- {
- switch(c)
- {
- case 'o':
- out_name = optarg;
- break;
- case 'v':
- verbose = true;
- break;
- }
- }
- argv += optind;
- argc -= optind;
- // FIXME: URL basename
- if(out_name == NULL) out_name = "body";
- out = open(out_name, out_flags, 0644);
- if(out < 0)
- {
- fprintf(stderr, "httpc: Failed opening destination file '%s': %s\n", out_name, strerror(errno));
- return 1;
- }
- if(argc != 1)
- {
- fprintf(stderr, "httpc: Expected 1 argument, got %d\n", argc);
- return 1;
- }
- char *errstr = NULL;
- struct URI *uri = decode_uri(argv[0], &errstr);
- if(uri == NULL)
- {
- fprintf(stderr, "httpc: Error while decoding <%s>: %s\n", argv[0], errstr);
- return 1;
- }
- struct tls *tls_ctx = tls_client();
- struct tls_config *tls_cfg = tls_config_new();
- if(tls_configure(tls_ctx, tls_cfg) != 0)
- {
- fprintf(stderr, "httpc: TLS Configuration Error: %s\n", tls_error(tls_ctx));
- decode_uri_finish(uri);
- tls_config_free(tls_cfg);
- return 1;
- }
- if(tls_connect(tls_ctx, uri->host, uri->port ? uri->port : "443") != 0)
- {
- fprintf(stderr, "httpc: TLS Connection Error: %s\n", tls_error(tls_ctx));
- decode_uri_finish(uri);
- tls_config_free(tls_cfg);
- return 1;
- }
- int err = 0;
- if(tls_handshake(tls_ctx) != 0)
- {
- fprintf(stderr, "httpc: TLS Handshake Error: %s\n", tls_error(tls_ctx));
- err = 1;
- goto cleanup;
- }
- static const char *req_fmt = "\
- GET %s HTTP/1.0\r\n\
- Host: %s\r\n\
- User-Agent: httpc/0.0\r\n\
- Connection: close\r\n\
- \r\n";
- char req[PAGESIZE];
- snprintf(req, PAGESIZE, req_fmt, uri->path ? uri->path : "/", uri->host);
- if(verbose) write(STDERR_FILENO, req, strlen(req));
- ssize_t nwrite = tls_write(tls_ctx, req, strlen(req));
- if(nwrite < 0)
- {
- fprintf(stderr, "httpc: Write Error: %s\n", tls_error(tls_ctx));
- err = 1;
- goto cleanup;
- }
- char res[PAGESIZE] = {};
- while(1)
- {
- ssize_t nread = tls_read(tls_ctx, res, PAGESIZE);
- if(nread == TLS_WANT_POLLIN || nread == TLS_WANT_POLLOUT)
- continue;
- if(nread < 0)
- {
- fprintf(stderr, "httpc: Write Error: %s\n", tls_error(tls_ctx));
- break;
- }
- if(nread == 0) break;
- ssize_t off = 0;
- // TODO: Read status
- while(body == 0 && off < nread) off += read_header(res+off, nread);
- if(body == 1) write(out, res+off, nread-off);
- }
- if(fsync(out) < 0)
- {
- fprintf(stderr, "httpc: Failed syncing destination file '%s' to disk: %s\n", out_name, strerror(errno));
- err = 1;
- }
- if(close(out) < 0)
- {
- fprintf(stderr, "httpc: Failed closing destination file '%s': %s\n", out_name, strerror(errno));
- err = 1;
- }
- cleanup:
- decode_uri_finish(uri);
- tls_close(tls_ctx);
- tls_free(tls_ctx);
- tls_config_free(tls_cfg);
- return err;
- }