char *host;
char *port;
char *path;
+ char *encoded_path;
char *query;
+ char *encoded_query;
char *fragment;
};
+
int tls_parse_uri(char *, struct tls_uri *);
void tls_free_uri(struct tls_uri *);
int match = (memcmp(certhash, fp, 64) == 0);
if (!match) {
fprintf(stderr, "host %s certificate changed\n", host);
+ fprintf(stderr, "was %.64s\n", fp);
+ fprintf(stderr, "now %.64s\n", certhash);
}
close(trustdb);
struct io {
struct tls_buffer response;
+ struct tls_buffer chunkbuf;
struct TLSContext *tls;
int socket;
+ int chunked;
+ int chunknum;
+ size_t chunksize;
+ size_t chunkleft;
+ size_t chunktotal;
+ size_t chunkbytesread;
int status_code;
time_t last_modified;
time_t date;
size_t content_length;
+ size_t received;
char *redirect;
};
+ssize_t unchunk(struct io *io);
int month(char *m) {
char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
hval[hlen] = '\r';
}
+ hval = find_header(io, "Transfer-Encoding:", &hlen);
+ if (hval) {
+ hval[hlen] = 0;
+ io->content_length = strtoul(hval, 0, 10);
+ if (!strcmp(hval, "chunked")) {
+ io->chunked = 1;
+ }
+ hval[hlen] = '\r';
+ }
+
switch (code) {
case 301:
case 302:
}
+/* fill buffer needs to put bytes into the response buffer
+ * if the transfer encoding is chunked, it will need to
+ * put the bytes into the chunkbuf first, then call
+ * unchunk. if unchunk return 0, then it needs more data,
+ * otherwise unchunk returns the number of bytes transferred
+ */
+
ssize_t fill_buffer(struct io *io) {
unsigned char buffer[4096];
- ssize_t ret;
+ ssize_t ret = 0;
- if (io->tls) {
- ret = tls_read(io->tls, buffer, sizeof buffer);
- } else {
- ret = read(io->socket, buffer, sizeof buffer);
- }
+ ret = unchunk(io);
+
+ while (ret == 0) {
+ if (io->tls) {
+ ret = tls_read(io->tls, buffer, sizeof buffer);
+ } else {
+ ret = read(io->socket, buffer, sizeof buffer);
+ }
- if (ret > 0) {
- tls_buffer_append(&io->response, buffer, ret);
+ if (ret <= 0) {
+ break;
+ }
+
+ if (io->chunked) {
+ tls_buffer_append(&io->chunkbuf, buffer, ret);
+ //fwrite(buffer, ret, 1, stderr);
+ ret = unchunk(io);
+ if (ret != 0 || io->chunksize == 0) {
+ break;
+ }
+ } else {
+ tls_buffer_append(&io->response, buffer, ret);
+ break;
+ }
}
return ret;
}
+/* essentially memmem */
+void *lookfor(const void *buf, size_t buflen, const void *pattern, size_t len) {
+ const char *bf = buf;
+ const char *pt = pattern;
+ const char *p = bf;
+
+ while (len <= (buflen - (p - bf))) {
+ if ((p = memchr(p, *pt, buflen - (p - bf))) != 0) {
+ if (memcmp(p, pattern, len) == 0) {
+ return (void *)p;
+ } else {
+ p++;
+ }
+ } else {
+ break;
+ }
+ }
+ return NULL;
+}
+
+/* returns read chunksize, unshifts the line */
+ssize_t read_chunksize(struct io *io) {
+ char *cr;
+ ssize_t cs;
+
+ //fwrite(io->chunkbuf.buffer, io->chunkbuf.len, 1, stderr);
+
+ /* there could be up to two leading bytes */
+ if (io->chunkbuf.len >= 2 && io->chunkbuf.buffer[0] == '\r' && io->chunkbuf.buffer[1] == '\n') {
+ tls_buffer_shift(&io->chunkbuf, 2);
+ }
+
+ cr = lookfor(io->chunkbuf.buffer, io->chunkbuf.len, "\r\n", 2);
+
+ if (cr == 0) {
+ return -1;
+ }
+
+ cs = strtol(io->chunkbuf.buffer, 0, 16);
+ tls_buffer_shift(&io->chunkbuf, cr - io->chunkbuf.buffer + 2);
+
+ return cs;
+}
+
+/* unchunk's job is to move bytes from the chunk buf to the response buf */
+/* return bytes from chunk, 0 if unable. once last chunk, changed chunked
+ * to 0?
+ */
+ssize_t unchunk(struct io *io) {
+ ssize_t bytes_to_move = 0;
+ ssize_t chunksize;
+
+ if (!io || !io->chunked) {
+ return 0;
+ }
+
+ if (io->chunkleft == 0) {
+ chunksize = read_chunksize(io);
+ if (chunksize == -1) {
+ return 0;
+ }
+ io->chunksize = chunksize;
+ if (io->chunksize == 0) {
+ /* end of chunked data */
+ io->chunked = 0;
+ return 0;
+ }
+ io->chunknum++;
+ io->chunkleft = io->chunksize;
+ io->chunktotal += io->chunksize;
+ }
+
+ if (io->chunkbuf.len == 0) {
+ /* need more bytes */
+ return 0;
+ }
+
+ bytes_to_move = io->chunkbuf.len < io->chunkleft ? io->chunkbuf.len : io->chunkleft;
+
+ tls_buffer_append(&io->response, io->chunkbuf.buffer, bytes_to_move);
+ io->chunkleft -= bytes_to_move;
+ io->chunkbytesread += bytes_to_move;
+
+ /* chunk is terminated with a crlf */
+ //tls_buffer_shift(&io->chunkbuf, bytes_to_move + io->chunkleft ? 0 : 2);
+ tls_buffer_shift(&io->chunkbuf, bytes_to_move);
+
+ return bytes_to_move;
+}
+
#if 0
char *nextline(struct io *io) {
char *eol = 0;;
int raw = 0, head = 0;
int out = 1; /* output file descriptor */
int use_tls = 0;
- struct io io = { {0}, 0, -1, 0, 0, 0, 0, 0 };
+ struct io io = { {0}, {0}, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
struct TLSContext *clientssl = 0;
int failsilent = 0;
char *lmfile = 0;
struct tls_buffer request;
char lmtime[80];
char *eoh = 0;
+ char *user_agent = 0;
size_t total = 0;
size_t header_len;
char *url = 0;
- int redirs = 0, redirlimit = 50, printstatus = 0;
+ int redirs = 0, redirlimit = 50, printstatus = 0, showreq = 0;
int verifypolicy = 1, calcoutfile = 0, ifnewer = 0;
ltc_mp = tfm_desc;
- while ((option = getopt(ac, av, "o:OrIfz:np#R:SkK")) != -1) {
+ while ((option = getopt(ac, av, "o:OrIfz:np#RL:SkKU:")) != -1) {
switch (option) {
case 'o': outfile = optarg; break;
case 'O': calcoutfile = 1; break;
case 'S': printstatus = 1; head = 1; break;
case 'k': verifypolicy = 0; break;
case 'K': verifypolicy = 2; break;
+ case 'U': user_agent = optarg; break;
case 'I': head = 1;
case 'r': raw = 1; break;
+ case 'R': showreq = 1; break;
case 'f': failsilent = 1; break;
case 'z': lmfile = optarg; break;
case 'n': ifnewer = 1; break;
- case 'R': redirlimit = strtol(optarg, 0, 10); break;
+ case 'L': redirlimit = strtol(optarg, 0, 10); break;
case 'p':
case '#': progressbar = 1; break;
default:
while (redirs++ <= redirlimit) {
tls_free_uri(&uri);
io.response.len = 0;
+ io.chunked = 0;
request.len = 0;
eoh = 0;
} else {
tls_buffer_append(&request, "GET ", 4);
}
- tls_buffer_append(&request, uri.path, strlen(uri.path));
+ tls_buffer_append(&request, uri.encoded_path, strlen(uri.encoded_path));
+ if (uri.encoded_query) {
+ tls_buffer_append(&request, "?", 1);
+ tls_buffer_append(&request, uri.encoded_query, strlen(uri.encoded_query));
+ }
tls_buffer_append(&request, " HTTP/1.1\r\n", 11);
append_header(&request, "Host", host);
+ if (user_agent) {
+ append_header(&request, "User-Agent", user_agent);
+ }
+ append_header(&request, "Accept", "*/*");
+ //append_header(&request, "Accept-Encoding", "chunked, identity;q=0.5");
append_header(&request, "Connection", "close");
if (lmfile) {
append_header(&request, "If-Modified-Since", lmtime);
io.socket = sockfd;
+ eoh = 0;
do {
if (io.response.len >= 4) {
eoh = strstr(io.response.buffer, "\r\n\r\n");
} while (!eoh);
if (!eoh) {
- /* never got (complet) header */
- fprintf(stderr, "incomplete response to %s\n", av[optind]);
+ /* never got (complete) header */
+ fprintf(stderr, "incomplete response (ret = %zd) to %s\n", ret, url);
+ fprintf(stderr, "have:\n");
+ fwrite(io.response.buffer, io.response.len, 1, stderr);
exit(EXIT_FAILURE);
}
header_len = (size_t)(eoh - io.response.buffer) + 4;
+
parse_header(&io);
switch (io.status_code) {
printf("%d\n", io.status_code);
break;
}
-
- if (!raw) {
- tls_buffer_shift(&io.response, header_len);
+ if (showreq) {
+ fwrite(request.buffer, request.len, 1, stderr);
}
+
if (head) {
io.response.len -= 2;
+ write(out, io.response.buffer, io.response.len);
+ break;
}
- if (progressbar) {
- if (io.content_length) {
- fprintf(stderr, "(%lu) ", io.content_length);
- }
+ if (io.status_code == 304) {
+ break;
}
if (outfile) {
- out = open(outfile, O_WRONLY|O_CREAT, 0600);
+ out = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0600);
if (out == -1) {
perror("can't open output file:");
exit(EXIT_FAILURE);
}
}
+ if (progressbar) {
+ if (io.content_length) {
+ fprintf(stderr, "(%lu) ", io.content_length);
+ }
+ }
+
+ if (raw) {
+ write(out, io.response.buffer, header_len);
+ }
+ tls_buffer_shift(&io.response, header_len);
+
+ if (io.chunked) {
+ /* we've written out the head if needed, so
+ * what's in the response buffer is the
+ * chunked encoding, so just reassign that
+ * to the chunkbuf and reinit */
+ io.chunkbuf = io.response;
+ tls_buffer_init(&io.response, 0);
+ /* and put whatever we've got into the response
+ * buffer, may not be needed, fill buffer
+ * can handle it.
+ */
+ //unchunk(&io);
+ }
+
do {
- write(out, io.response.buffer, io.response.len);
- ret = io.response.len;
- io.response.len = 0;
+ size_t before = io.received;
+ if (io.response.len) {
+ if (io.content_length && io.response.len + io.received > io.content_length) {
+ io.response.len = io.content_length - io.received;
+ /* we just ignore trailing garbage */
+ }
+ write(out, io.response.buffer, io.response.len);
+ io.received += io.response.len;
+ ret = io.response.len;
+ io.response.len = 0;
+ }
if (progressbar) {
if (io.content_length) {
- pdots(50, '.', total, total+ret,
+ pdots(50, '.', before, io.received,
io.content_length);
} else {
putc('\r', stderr);
- fprintf(stderr, "%zu", total+ret);
+ fprintf(stderr, "%zu", io.received);
}
total += ret;
}
if (head) {
break;
}
+ if (io.content_length && io.received >= io.content_length) {
+ break;
+ }
ret = fill_buffer(&io);
} while (ret > 0);
+ //fprintf(stderr, "total received: %zu/%zu\n", io.received, io.content_length);
if (ret < 0) {
fprintf(stderr, "%s read error %zd\n", uri.scheme, ret);
}
- struct timespec ts[2];
- ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_OMIT;
- ts[1].tv_sec = io.last_modified;
- ts[1].tv_nsec = 0;
-
- futimens(out, ts);
+ if (io.last_modified != 0) {
+ struct timespec ts[2];
+ ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_OMIT;
+ ts[1].tv_sec = io.last_modified;
+ ts[1].tv_nsec = 0;
+ futimens(out, ts);
+ }
close(out);
tls_buffer_free(&io.response);
break;
close(sockfd);
if (progressbar && io.status_code == 200) {
- fprintf(stderr, "(%lu)", total);
- putc('\n',stderr);
+ if (io.received == io.content_length || io.content_length == 0) {
+ fprintf(stderr, " done\n");
+ } else if (io.content_length != io.received) {
+ fprintf(stderr, "failed (%zu bytes read)\n", total);
+ io.status_code = 531; /* non official code */
+ }
}
return io.status_code < 400 ? 0 : EXIT_FAILURE;