2017-06-29 09:40:48 +00:00
|
|
|
/* $Id: http.c,v 1.20 2017/03/26 18:41:02 deraadt Exp $ */
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <err.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <tls.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "http.h"
|
2017-06-30 19:30:05 +00:00
|
|
|
#include "main.h"
|
2017-06-29 09:40:48 +00:00
|
|
|
|
|
|
|
#define DEFAULT_CA_FILE "/etc/ssl/cert.pem"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A buffer for transferring HTTP/S data.
|
|
|
|
*/
|
|
|
|
struct httpxfer {
|
|
|
|
char *hbuf; /* header transfer buffer */
|
|
|
|
size_t hbufsz; /* header buffer size */
|
|
|
|
int headok; /* header has been parsed */
|
|
|
|
char *bbuf; /* body transfer buffer */
|
|
|
|
size_t bbufsz; /* body buffer size */
|
|
|
|
int bodyok; /* body has been parsed */
|
|
|
|
char *headbuf; /* lookaside buffer for headers */
|
|
|
|
struct httphead *head; /* parsed headers */
|
|
|
|
size_t headsz; /* number of headers */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An HTTP/S connection object.
|
|
|
|
*/
|
|
|
|
struct http {
|
|
|
|
int fd; /* connected socket */
|
|
|
|
short port; /* port number */
|
|
|
|
struct source src; /* endpoint (raw) host */
|
|
|
|
char *path; /* path to request */
|
|
|
|
char *host; /* name of endpoint host */
|
|
|
|
struct tls *ctx; /* if TLS */
|
|
|
|
writefp writer; /* write function */
|
|
|
|
readfp reader; /* read function */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct tls_config *tlscfg;
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
dosysread(char *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t rc;
|
|
|
|
|
|
|
|
rc = read(http->fd, buf, sz);
|
|
|
|
if (rc < 0)
|
|
|
|
warn("%s: read", http->src.ip);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
dosyswrite(const void *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t rc;
|
|
|
|
|
|
|
|
rc = write(http->fd, buf, sz);
|
|
|
|
if (rc < 0)
|
|
|
|
warn("%s: write", http->src.ip);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
dotlsread(char *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t rc;
|
|
|
|
|
|
|
|
do {
|
|
|
|
rc = tls_read(http->ctx, buf, sz);
|
|
|
|
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
|
|
|
|
|
|
|
if (rc < 0)
|
|
|
|
warnx("%s: tls_read: %s", http->src.ip,
|
|
|
|
tls_error(http->ctx));
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
dotlswrite(const void *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t rc;
|
|
|
|
|
|
|
|
do {
|
|
|
|
rc = tls_write(http->ctx, buf, sz);
|
|
|
|
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
|
|
|
|
|
|
|
if (rc < 0)
|
|
|
|
warnx("%s: tls_write: %s", http->src.ip,
|
|
|
|
tls_error(http->ctx));
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
http_init()
|
|
|
|
{
|
|
|
|
if (tlscfg != NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (tls_init() == -1) {
|
|
|
|
warn("tls_init");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
tlscfg = tls_config_new();
|
|
|
|
if (tlscfg == NULL) {
|
|
|
|
warn("tls_config_new");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tls_config_set_ca_file(tlscfg, DEFAULT_CA_FILE) == -1) {
|
|
|
|
warn("tls_config_set_ca_file: %s", tls_config_error(tlscfg));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
tls_config_free(tlscfg);
|
|
|
|
tlscfg = NULL;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
http_read(char *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t ssz, xfer;
|
|
|
|
|
|
|
|
xfer = 0;
|
|
|
|
do {
|
|
|
|
if ((ssz = http->reader(buf, sz, http)) < 0)
|
|
|
|
return -1;
|
|
|
|
if (ssz == 0)
|
|
|
|
break;
|
|
|
|
xfer += ssz;
|
|
|
|
sz -= ssz;
|
|
|
|
buf += ssz;
|
|
|
|
} while (ssz > 0 && sz > 0);
|
|
|
|
|
|
|
|
return xfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
http_write(const char *buf, size_t sz, const struct http *http)
|
|
|
|
{
|
|
|
|
ssize_t ssz, xfer;
|
|
|
|
|
|
|
|
xfer = sz;
|
|
|
|
while (sz > 0) {
|
|
|
|
if ((ssz = http->writer(buf, sz, http)) < 0)
|
|
|
|
return -1;
|
|
|
|
sz -= ssz;
|
|
|
|
buf += (size_t)ssz;
|
|
|
|
}
|
|
|
|
return xfer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
http_disconnect(struct http *http)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (http->ctx != NULL) {
|
|
|
|
/* TLS connection. */
|
|
|
|
do {
|
|
|
|
rc = tls_close(http->ctx);
|
|
|
|
} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
|
|
|
|
|
|
|
|
if (rc < 0)
|
|
|
|
warnx("%s: tls_close: %s", http->src.ip,
|
|
|
|
tls_error(http->ctx));
|
|
|
|
|
|
|
|
tls_free(http->ctx);
|
|
|
|
}
|
|
|
|
if (http->fd != -1) {
|
|
|
|
if (close(http->fd) == -1)
|
|
|
|
warn("%s: close", http->src.ip);
|
|
|
|
}
|
|
|
|
|
|
|
|
http->fd = -1;
|
|
|
|
http->ctx = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
http_free(struct http *http)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (http == NULL)
|
|
|
|
return;
|
|
|
|
http_disconnect(http);
|
|
|
|
free(http->host);
|
|
|
|
free(http->path);
|
|
|
|
free(http->src.ip);
|
|
|
|
free(http);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct http *
|
|
|
|
http_alloc(const struct source *addrs, size_t addrsz,
|
|
|
|
const char *host, short port, const char *path)
|
|
|
|
{
|
|
|
|
struct sockaddr_storage ss;
|
|
|
|
int family, fd, c;
|
|
|
|
socklen_t len;
|
|
|
|
size_t cur, i = 0;
|
|
|
|
struct http *http;
|
|
|
|
|
|
|
|
/* Do this while we still have addresses to connect. */
|
|
|
|
again:
|
|
|
|
if (i == addrsz)
|
|
|
|
return NULL;
|
|
|
|
cur = i++;
|
|
|
|
|
|
|
|
/* Convert to PF_INET or PF_INET6 address from string. */
|
|
|
|
|
|
|
|
memset(&ss, 0, sizeof(struct sockaddr_storage));
|
|
|
|
|
|
|
|
if (addrs[cur].family == 4) {
|
|
|
|
family = PF_INET;
|
|
|
|
((struct sockaddr_in *)&ss)->sin_family = AF_INET;
|
|
|
|
((struct sockaddr_in *)&ss)->sin_port = htons(port);
|
|
|
|
c = inet_pton(AF_INET, addrs[cur].ip,
|
|
|
|
&((struct sockaddr_in *)&ss)->sin_addr);
|
|
|
|
len = sizeof(struct sockaddr_in);
|
|
|
|
} else if (addrs[cur].family == 6) {
|
|
|
|
family = PF_INET6;
|
|
|
|
((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
|
|
|
|
((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
|
|
|
|
c = inet_pton(AF_INET6, addrs[cur].ip,
|
|
|
|
&((struct sockaddr_in6 *)&ss)->sin6_addr);
|
|
|
|
len = sizeof(struct sockaddr_in6);
|
|
|
|
} else {
|
|
|
|
warnx("%s: unknown family", addrs[cur].ip);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c < 0) {
|
|
|
|
warn("%s: inet_ntop", addrs[cur].ip);
|
|
|
|
goto again;
|
|
|
|
} else if (c == 0) {
|
|
|
|
warnx("%s: inet_ntop", addrs[cur].ip);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create socket and connect. */
|
|
|
|
|
|
|
|
fd = socket(family, SOCK_STREAM, 0);
|
|
|
|
if (fd == -1) {
|
|
|
|
warn("%s: socket", addrs[cur].ip);
|
|
|
|
goto again;
|
|
|
|
} else if (connect(fd, (struct sockaddr *)&ss, len) == -1) {
|
|
|
|
warn("%s: connect", addrs[cur].ip);
|
|
|
|
close(fd);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate the communicator. */
|
|
|
|
|
|
|
|
http = calloc(1, sizeof(struct http));
|
|
|
|
if (http == NULL) {
|
|
|
|
warn("calloc");
|
|
|
|
close(fd);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
http->fd = fd;
|
|
|
|
http->port = port;
|
|
|
|
http->src.family = addrs[cur].family;
|
|
|
|
http->src.ip = strdup(addrs[cur].ip);
|
|
|
|
http->host = strdup(host);
|
|
|
|
http->path = strdup(path);
|
|
|
|
if (http->src.ip == NULL || http->host == NULL || http->path == NULL) {
|
|
|
|
warn("strdup");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If necessary, do our TLS setup. */
|
|
|
|
|
|
|
|
if (port != 443) {
|
|
|
|
http->writer = dosyswrite;
|
|
|
|
http->reader = dosysread;
|
|
|
|
return http;
|
|
|
|
}
|
|
|
|
|
|
|
|
http->writer = dotlswrite;
|
|
|
|
http->reader = dotlsread;
|
|
|
|
|
|
|
|
if ((http->ctx = tls_client()) == NULL) {
|
|
|
|
warn("tls_client");
|
|
|
|
goto err;
|
|
|
|
} else if (tls_configure(http->ctx, tlscfg) == -1) {
|
|
|
|
warnx("%s: tls_configure: %s",
|
|
|
|
http->src.ip, tls_error(http->ctx));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tls_connect_socket(http->ctx, http->fd, http->host) != 0) {
|
|
|
|
warnx("%s: tls_connect_socket: %s, %s", http->src.ip,
|
|
|
|
http->host, tls_error(http->ctx));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return http;
|
|
|
|
err:
|
|
|
|
http_free(http);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
http_head_write(struct httphead **head, const struct http *http)
|
|
|
|
{
|
|
|
|
char *req = NULL;
|
|
|
|
int i, c;
|
|
|
|
|
|
|
|
if (head == NULL)
|
|
|
|
return (0);
|
|
|
|
|
|
|
|
for (i = 0; head[i] != NULL && head[i]->key != NULL; i++) {
|
|
|
|
/* Append terminating \r\n after last header line */
|
|
|
|
c = asprintf(&req, "%s: %s\r\n%s", head[i]->key, head[i]->val,
|
|
|
|
head[i + 1] == NULL ? "\r\n" : "");
|
|
|
|
if (!http_write(req, c, http)) {
|
|
|
|
free(req);
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
free(req);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct httpxfer *
|
|
|
|
http_open(const struct http *http, const void *p, size_t psz,
|
|
|
|
struct httphead **reqhead)
|
|
|
|
{
|
|
|
|
char *req;
|
|
|
|
int c;
|
|
|
|
struct httpxfer *trans;
|
|
|
|
|
|
|
|
if (p == NULL) {
|
|
|
|
c = asprintf(&req,
|
|
|
|
"GET %s HTTP/1.0\r\n"
|
|
|
|
"Host: %s\r\n%s",
|
|
|
|
http->path, http->host,
|
|
|
|
reqhead != NULL ? "" : "\r\n");
|
|
|
|
} else {
|
|
|
|
c = asprintf(&req,
|
|
|
|
"POST %s HTTP/1.0\r\n"
|
|
|
|
"Host: %s\r\n"
|
|
|
|
"Content-Length: %zu\r\n%s",
|
|
|
|
http->path, http->host, psz,
|
|
|
|
reqhead != NULL ? "" : "\r\n");
|
|
|
|
}
|
|
|
|
if (c == -1) {
|
|
|
|
warn("asprintf");
|
|
|
|
return NULL;
|
|
|
|
} else if (!http_write(req, c, http)) {
|
|
|
|
free(req);
|
|
|
|
return NULL;
|
|
|
|
} else if (http_head_write(reqhead, http) != 0) {
|
|
|
|
free(req);
|
|
|
|
return NULL;
|
|
|
|
} else if (p != NULL && !http_write(p, psz, http)) {
|
|
|
|
free(req);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(req);
|
|
|
|
|
|
|
|
trans = calloc(1, sizeof(struct httpxfer));
|
|
|
|
if (trans == NULL)
|
|
|
|
warn("calloc");
|
|
|
|
return trans;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
http_close(struct httpxfer *x)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (x == NULL)
|
|
|
|
return;
|
|
|
|
free(x->hbuf);
|
|
|
|
free(x->bbuf);
|
|
|
|
free(x->headbuf);
|
|
|
|
free(x->head);
|
|
|
|
free(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the HTTP body from the wire.
|
|
|
|
* If invoked multiple times, this will return the same pointer with the
|
|
|
|
* same data (or NULL, if the original invocation returned NULL).
|
|
|
|
* Returns NULL if read or allocation errors occur.
|
|
|
|
* You must not free the returned pointer.
|
|
|
|
*/
|
|
|
|
char *
|
|
|
|
http_body_read(const struct http *http, struct httpxfer *trans, size_t *sz)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
ssize_t ssz;
|
|
|
|
void *pp;
|
|
|
|
size_t szp;
|
|
|
|
|
|
|
|
if (sz == NULL)
|
|
|
|
sz = &szp;
|
|
|
|
|
|
|
|
/* Have we already parsed this? */
|
|
|
|
|
|
|
|
if (trans->bodyok > 0) {
|
|
|
|
*sz = trans->bbufsz;
|
|
|
|
return trans->bbuf;
|
|
|
|
} else if (trans->bodyok < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*sz = 0;
|
|
|
|
trans->bodyok = -1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* If less than sizeof(buf), at EOF. */
|
|
|
|
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
|
|
|
|
return NULL;
|
|
|
|
else if (ssz == 0)
|
|
|
|
break;
|
|
|
|
pp = recallocarray(trans->bbuf,
|
|
|
|
trans->bbufsz, trans->bbufsz + ssz, 1);
|
|
|
|
if (pp == NULL) {
|
|
|
|
warn("recallocarray");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
trans->bbuf = pp;
|
|
|
|
memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
|
|
|
|
trans->bbufsz += ssz;
|
|
|
|
} while (ssz == sizeof(buf));
|
|
|
|
|
|
|
|
trans->bodyok = 1;
|
|
|
|
*sz = trans->bbufsz;
|
|
|
|
return trans->bbuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct httphead *
|
|
|
|
http_head_get(const char *v, struct httphead *h, size_t hsz)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < hsz; i++) {
|
|
|
|
if (strcmp(h[i].key, v))
|
|
|
|
continue;
|
|
|
|
return &h[i];
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Look through the headers and determine our HTTP code.
|
|
|
|
* This will return -1 on failure, otherwise the code.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
http_head_status(const struct http *http, struct httphead *h, size_t sz)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
unsigned int code;
|
|
|
|
struct httphead *st;
|
|
|
|
|
|
|
|
if ((st = http_head_get("Status", h, sz)) == NULL) {
|
|
|
|
warnx("%s: no status header", http->src.ip);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = sscanf(st->val, "%*s %u %*s", &code);
|
|
|
|
if (rc < 0) {
|
|
|
|
warn("sscanf");
|
|
|
|
return -1;
|
|
|
|
} else if (rc != 1) {
|
|
|
|
warnx("%s: cannot convert status header", http->src.ip);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parse headers from the transfer.
|
|
|
|
* Malformed headers are skipped.
|
|
|
|
* A special "Status" header is added for the HTTP status line.
|
|
|
|
* This can only happen once http_head_read has been called with
|
|
|
|
* success.
|
|
|
|
* This can be invoked multiple times: it will only parse the headers
|
|
|
|
* once and after that it will just return the cache.
|
|
|
|
* You must not free the returned pointer.
|
|
|
|
* If the original header parse failed, or if memory allocation fails
|
|
|
|
* internally, this returns NULL.
|
|
|
|
*/
|
|
|
|
struct httphead *
|
|
|
|
http_head_parse(const struct http *http, struct httpxfer *trans, size_t *sz)
|
|
|
|
{
|
|
|
|
size_t hsz, szp;
|
|
|
|
struct httphead *h;
|
|
|
|
char *cp, *ep, *ccp, *buf;
|
|
|
|
|
|
|
|
if (sz == NULL)
|
|
|
|
sz = &szp;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we've already parsed the headers, return the
|
|
|
|
* previously-parsed buffer now.
|
|
|
|
* If we have errors on the stream, return NULL now.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (trans->head != NULL) {
|
|
|
|
*sz = trans->headsz;
|
|
|
|
return trans->head;
|
|
|
|
} else if (trans->headok <= 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if ((buf = strdup(trans->hbuf)) == NULL) {
|
|
|
|
warn("strdup");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
hsz = 0;
|
|
|
|
cp = buf;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if ((cp = strstr(cp, "\r\n")) != NULL)
|
|
|
|
cp += 2;
|
|
|
|
hsz++;
|
|
|
|
} while (cp != NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate headers, then step through the data buffer, parsing
|
|
|
|
* out headers as we have them.
|
|
|
|
* We know at this point that the buffer is NUL-terminated in
|
|
|
|
* the usual way.
|
|
|
|
*/
|
|
|
|
|
|
|
|
h = calloc(hsz, sizeof(struct httphead));
|
|
|
|
if (h == NULL) {
|
|
|
|
warn("calloc");
|
|
|
|
free(buf);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
*sz = hsz;
|
|
|
|
hsz = 0;
|
|
|
|
cp = buf;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if ((ep = strstr(cp, "\r\n")) != NULL) {
|
|
|
|
*ep = '\0';
|
|
|
|
ep += 2;
|
|
|
|
}
|
|
|
|
if (hsz == 0) {
|
|
|
|
h[hsz].key = "Status";
|
|
|
|
h[hsz++].val = cp;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip bad headers. */
|
|
|
|
if ((ccp = strchr(cp, ':')) == NULL) {
|
|
|
|
warnx("%s: header without separator", http->src.ip);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ccp++ = '\0';
|
2017-06-30 19:30:05 +00:00
|
|
|
while (isspace((unsigned char)*ccp))
|
2017-06-29 09:40:48 +00:00
|
|
|
ccp++;
|
|
|
|
h[hsz].key = cp;
|
|
|
|
h[hsz++].val = ccp;
|
|
|
|
} while ((cp = ep) != NULL);
|
|
|
|
|
|
|
|
trans->headbuf = buf;
|
|
|
|
trans->head = h;
|
|
|
|
trans->headsz = hsz;
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the HTTP headers from the wire.
|
|
|
|
* If invoked multiple times, this will return the same pointer with the
|
|
|
|
* same data (or NULL, if the original invocation returned NULL).
|
|
|
|
* Returns NULL if read or allocation errors occur.
|
|
|
|
* You must not free the returned pointer.
|
|
|
|
*/
|
|
|
|
char *
|
|
|
|
http_head_read(const struct http *http, struct httpxfer *trans, size_t *sz)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
ssize_t ssz;
|
|
|
|
char *ep;
|
|
|
|
void *pp;
|
|
|
|
size_t szp;
|
|
|
|
|
|
|
|
if (sz == NULL)
|
|
|
|
sz = &szp;
|
|
|
|
|
|
|
|
/* Have we already parsed this? */
|
|
|
|
|
|
|
|
if (trans->headok > 0) {
|
|
|
|
*sz = trans->hbufsz;
|
|
|
|
return trans->hbuf;
|
|
|
|
} else if (trans->headok < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*sz = 0;
|
|
|
|
ep = NULL;
|
|
|
|
trans->headok = -1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Begin by reading by BUFSIZ blocks until we reach the header
|
|
|
|
* termination marker (two CRLFs).
|
|
|
|
* We might read into our body, but that's ok: we'll copy out
|
|
|
|
* the body parts into our body buffer afterward.
|
|
|
|
*/
|
|
|
|
|
|
|
|
do {
|
|
|
|
/* If less than sizeof(buf), at EOF. */
|
|
|
|
if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
|
|
|
|
return NULL;
|
|
|
|
else if (ssz == 0)
|
|
|
|
break;
|
|
|
|
pp = recallocarray(trans->hbuf,
|
|
|
|
trans->hbufsz, trans->hbufsz + ssz, 1);
|
|
|
|
if (pp == NULL) {
|
|
|
|
warn("recallocarray");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
trans->hbuf = pp;
|
|
|
|
memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
|
|
|
|
trans->hbufsz += ssz;
|
|
|
|
/* Search for end of headers marker. */
|
|
|
|
ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
|
|
|
|
} while (ep == NULL && ssz == sizeof(buf));
|
|
|
|
|
|
|
|
if (ep == NULL) {
|
|
|
|
warnx("%s: partial transfer", http->src.ip);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
*ep = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The header data is invalid if it has any binary characters in
|
|
|
|
* it: check that now.
|
|
|
|
* This is important because we want to guarantee that all
|
|
|
|
* header keys and pairs are properly NUL-terminated.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
|
|
|
|
warnx("%s: binary data in header", http->src.ip);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy remaining buffer into body buffer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
ep += 4;
|
|
|
|
trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
|
|
|
|
trans->bbuf = malloc(trans->bbufsz);
|
|
|
|
if (trans->bbuf == NULL) {
|
|
|
|
warn("malloc");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
memcpy(trans->bbuf, ep, trans->bbufsz);
|
|
|
|
|
|
|
|
trans->headok = 1;
|
|
|
|
*sz = trans->hbufsz;
|
|
|
|
return trans->hbuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
http_get_free(struct httpget *g)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (g == NULL)
|
|
|
|
return;
|
|
|
|
http_close(g->xfer);
|
|
|
|
http_free(g->http);
|
|
|
|
free(g);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct httpget *
|
|
|
|
http_get(const struct source *addrs, size_t addrsz, const char *domain,
|
|
|
|
short port, const char *path, const void *post, size_t postsz,
|
|
|
|
struct httphead **reqhead)
|
|
|
|
{
|
|
|
|
struct http *h;
|
|
|
|
struct httpxfer *x;
|
|
|
|
struct httpget *g;
|
|
|
|
struct httphead *head;
|
|
|
|
size_t headsz, bodsz, headrsz;
|
|
|
|
int code;
|
|
|
|
char *bod, *headr;
|
|
|
|
|
|
|
|
h = http_alloc(addrs, addrsz, domain, port, path);
|
|
|
|
if (h == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if ((x = http_open(h, post, postsz, reqhead)) == NULL) {
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
} else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {
|
|
|
|
http_close(x);
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
} else if ((bod = http_body_read(h, x, &bodsz)) == NULL) {
|
|
|
|
http_close(x);
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
http_disconnect(h);
|
|
|
|
|
|
|
|
if ((head = http_head_parse(h, x, &headsz)) == NULL) {
|
|
|
|
http_close(x);
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
} else if ((code = http_head_status(h, head, headsz)) < 0) {
|
|
|
|
http_close(x);
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((g = calloc(1, sizeof(struct httpget))) == NULL) {
|
|
|
|
warn("calloc");
|
|
|
|
http_close(x);
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
g->headpart = headr;
|
|
|
|
g->headpartsz = headrsz;
|
|
|
|
g->bodypart = bod;
|
|
|
|
g->bodypartsz = bodsz;
|
|
|
|
g->head = head;
|
|
|
|
g->headsz = headsz;
|
|
|
|
g->code = code;
|
|
|
|
g->xfer = x;
|
|
|
|
g->http = h;
|
|
|
|
return g;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
int
|
|
|
|
main(void)
|
|
|
|
{
|
|
|
|
struct httpget *g;
|
|
|
|
struct httphead *httph;
|
|
|
|
size_t i, httphsz;
|
|
|
|
struct source addrs[2];
|
|
|
|
size_t addrsz;
|
|
|
|
struct httphead *reqhead[3];
|
|
|
|
|
|
|
|
/* This could be allocated ;) */
|
|
|
|
reqhead[0] = &(struct httphead){ "X-Hello", "World" };
|
|
|
|
reqhead[1] = &(struct httphead){ "X-Test", "123" };
|
|
|
|
reqhead[2] = NULL;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
addrs[0].ip = "127.0.0.1";
|
|
|
|
addrs[0].family = 4;
|
|
|
|
addrsz = 1;
|
|
|
|
#else
|
|
|
|
addrs[0].ip = "2a00:1450:400a:806::2004";
|
|
|
|
addrs[0].family = 6;
|
|
|
|
addrs[1].ip = "193.135.3.123";
|
|
|
|
addrs[1].family = 4;
|
|
|
|
addrsz = 2;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (http_init() == -1)
|
|
|
|
errx(EXIT_FAILURE, "http_init");
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
g = http_get(addrs, addrsz, "localhost", 80, "/index.html",
|
|
|
|
NULL, 0, reqhead);
|
|
|
|
#else
|
|
|
|
g = http_get(addrs, addrsz, "www.google.ch", 80, "/index.html",
|
|
|
|
NULL, 0, reqhead);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (g == NULL)
|
|
|
|
errx(EXIT_FAILURE, "http_get");
|
|
|
|
|
|
|
|
httph = http_head_parse(g->http, g->xfer, &httphsz);
|
|
|
|
warnx("code: %d", g->code);
|
|
|
|
|
|
|
|
for (i = 0; i < httphsz; i++)
|
|
|
|
warnx("head: [%s]=[%s]", httph[i].key, httph[i].val);
|
|
|
|
|
|
|
|
http_get_free(g);
|
|
|
|
return (EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
#endif
|