1783 lines
46 KiB
C
1783 lines
46 KiB
C
/*
|
|
* Tvheadend - HTTP client functions
|
|
*
|
|
* Copyright (C) 2014 Jaroslav Kysela
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "tvheadend.h"
|
|
#include "http.h"
|
|
#include "tcp.h"
|
|
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
#ifndef SSL_OP_NO_COMPRESSION
|
|
#define SSL_OP_NO_COMPRESSION 0
|
|
#endif
|
|
|
|
struct http_client_ssl {
|
|
int connected;
|
|
int shutdown;
|
|
int notified;
|
|
|
|
SSL_CTX *ctx;
|
|
SSL *ssl;
|
|
|
|
BIO *rbio;
|
|
char *rbio_buf;
|
|
size_t rbio_size;
|
|
size_t rbio_pos;
|
|
|
|
BIO *wbio;
|
|
char *wbio_buf;
|
|
size_t wbio_size;
|
|
size_t wbio_pos;
|
|
};
|
|
|
|
|
|
static int
|
|
http_client_redirected ( http_client_t *hc );
|
|
static int
|
|
http_client_ssl_write_update( http_client_t *hc );
|
|
static int
|
|
http_client_reconnect
|
|
( http_client_t *hc, http_ver_t ver, const char *scheme,
|
|
const char *host, int port );
|
|
#if HTTPCLIENT_TESTSUITE
|
|
static void
|
|
http_client_testsuite_run( void );
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Global state
|
|
*/
|
|
static int http_running;
|
|
static tvhpoll_t *http_poll;
|
|
static TAILQ_HEAD(,http_client) http_clients;
|
|
static pthread_mutex_t http_lock;
|
|
static pthread_cond_t http_cond;
|
|
static th_pipe_t http_pipe;
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static int
|
|
http_port( const char *scheme, int port )
|
|
{
|
|
if (port <= 0 || port > 65535) {
|
|
if (scheme && strcmp(scheme, "http") == 0)
|
|
port = 80;
|
|
else if (scheme && strcmp(scheme, "https") == 0)
|
|
port = 443;
|
|
else if (scheme && strcmp(scheme, "rtsp") == 0)
|
|
port = 554;
|
|
else {
|
|
tvhlog(LOG_ERR, "httpc", "Unknown scheme '%s'", scheme ? scheme : "");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return port;
|
|
}
|
|
|
|
/*
|
|
* Disable
|
|
*/
|
|
static void
|
|
http_client_shutdown ( http_client_t *hc, int force, int reconnect )
|
|
{
|
|
struct http_client_ssl *ssl = hc->hc_ssl;
|
|
tvhpoll_t *efd = NULL;
|
|
|
|
hc->hc_shutdown = 1;
|
|
if (ssl) {
|
|
if (!ssl->shutdown) {
|
|
SSL_shutdown(hc->hc_ssl->ssl);
|
|
http_client_ssl_write_update(hc);
|
|
ssl->shutdown = 1;
|
|
}
|
|
if (!force)
|
|
return;
|
|
}
|
|
if (hc->hc_efd) {
|
|
tvhpoll_event_t ev;
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.fd = hc->hc_fd;
|
|
tvhpoll_rem(efd = hc->hc_efd, &ev, 1);
|
|
if (hc->hc_efd == http_poll && !reconnect) {
|
|
pthread_mutex_lock(&http_lock);
|
|
TAILQ_REMOVE(&http_clients, hc, hc_link);
|
|
hc->hc_efd = NULL;
|
|
pthread_mutex_unlock(&http_lock);
|
|
} else {
|
|
hc->hc_efd = NULL;
|
|
}
|
|
}
|
|
if (hc->hc_fd >= 0) {
|
|
if (hc->hc_conn_closed)
|
|
hc->hc_conn_closed(hc, -hc->hc_result);
|
|
if (hc->hc_fd >= 0)
|
|
close(hc->hc_fd);
|
|
hc->hc_fd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Poll I/O
|
|
*/
|
|
static void
|
|
http_client_poll_dir ( http_client_t *hc, int in, int out )
|
|
{
|
|
int events = (in ? TVHPOLL_IN : 0) | (out ? TVHPOLL_OUT : 0);
|
|
if (hc->hc_efd && hc->hc_pevents != events) {
|
|
tvhpoll_event_t ev;
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.fd = hc->hc_fd;
|
|
ev.events = events | TVHPOLL_IN;
|
|
ev.data.ptr = hc;
|
|
tvhpoll_add(hc->hc_efd, &ev, 1);
|
|
}
|
|
hc->hc_pevents = events;
|
|
/* make sure to se the correct errno for our SSL routines */
|
|
errno = EAGAIN;
|
|
}
|
|
|
|
static void
|
|
http_client_direction ( http_client_t *hc, int sending )
|
|
{
|
|
hc->hc_sending = sending;
|
|
if (hc->hc_ssl == NULL)
|
|
http_client_poll_dir(hc, 1, sending);
|
|
}
|
|
|
|
/*
|
|
* Main I/O routines
|
|
*/
|
|
|
|
static void
|
|
http_client_cmd_destroy( http_client_t *hc, http_client_wcmd_t *cmd )
|
|
{
|
|
TAILQ_REMOVE(&hc->hc_wqueue, cmd, link);
|
|
free(cmd->wbuf);
|
|
free(cmd);
|
|
}
|
|
|
|
static int
|
|
http_client_flush( http_client_t *hc, int result )
|
|
{
|
|
hc->hc_result = result;
|
|
if (result < 0)
|
|
http_client_shutdown(hc, 0, 0);
|
|
hc->hc_in_data = 0;
|
|
hc->hc_hsize = 0;
|
|
hc->hc_csize = 0;
|
|
hc->hc_rpos = 0;
|
|
hc->hc_chunked = 0;
|
|
free(hc->hc_chunk);
|
|
hc->hc_chunk = 0;
|
|
hc->hc_chunk_pos = 0;
|
|
hc->hc_chunk_size = 0;
|
|
hc->hc_chunk_csize = 0;
|
|
hc->hc_chunk_alloc = 0;
|
|
hc->hc_chunk_trails = 0;
|
|
http_arg_flush(&hc->hc_args);
|
|
return result;
|
|
}
|
|
|
|
int
|
|
http_client_clear_state( http_client_t *hc )
|
|
{
|
|
if (hc->hc_shutdown)
|
|
return -EBADF;
|
|
free(hc->hc_data);
|
|
hc->hc_data = NULL;
|
|
hc->hc_data_size = 0;
|
|
return http_client_flush(hc, 0);
|
|
}
|
|
|
|
static int
|
|
http_client_ssl_read_update( http_client_t *hc )
|
|
{
|
|
struct http_client_ssl *ssl = hc->hc_ssl;
|
|
char *rbuf = alloca(hc->hc_io_size);
|
|
ssize_t r, r2;
|
|
size_t len;
|
|
|
|
if (ssl->rbio_pos > 0) {
|
|
r = BIO_write(ssl->rbio, ssl->rbio_buf, ssl->rbio_pos);
|
|
if (r >= 0) {
|
|
memmove(ssl->rbio_buf, ssl->rbio_buf + r, ssl->rbio_pos - r);
|
|
ssl->rbio_pos -= r;
|
|
} else if (r < 0) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
}
|
|
r = recv(hc->hc_fd, rbuf, hc->hc_io_size, MSG_DONTWAIT);
|
|
if (r == 0) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
if (r < 0) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
|
|
http_client_poll_dir(hc, 1, 0);
|
|
errno = EAGAIN;
|
|
return r;
|
|
}
|
|
return r;
|
|
}
|
|
r2 = BIO_write(ssl->rbio, rbuf, r);
|
|
len = r - (r2 < 0 ? 0 : r2);
|
|
if (len) {
|
|
if (ssl->rbio_pos + len > ssl->rbio_size) {
|
|
ssl->rbio_buf = realloc(ssl->rbio_buf, ssl->rbio_pos + len);
|
|
ssl->rbio_size += len;
|
|
}
|
|
memcpy(ssl->rbio_buf + ssl->rbio_pos, rbuf + (len - r), len);
|
|
ssl->rbio_pos += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
http_client_ssl_write_update( http_client_t *hc )
|
|
{
|
|
struct http_client_ssl *ssl = hc->hc_ssl;
|
|
char *rbuf = alloca(hc->hc_io_size);
|
|
ssize_t r, r2;
|
|
size_t len;
|
|
|
|
if (ssl->wbio_pos) {
|
|
r = send(hc->hc_fd, ssl->wbio_buf, ssl->wbio_pos, MSG_DONTWAIT);
|
|
if (r > 0) {
|
|
memmove(ssl->wbio_buf, ssl->wbio_buf + r, ssl->wbio_pos - r);
|
|
ssl->wbio_pos -= r;
|
|
} else if (r < 0) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
|
|
http_client_poll_dir(hc, 0, 1);
|
|
errno = EAGAIN;
|
|
return r;
|
|
}
|
|
return r;
|
|
}
|
|
if (ssl->wbio_pos)
|
|
return 1;
|
|
}
|
|
r = BIO_read(ssl->wbio, rbuf, hc->hc_io_size);
|
|
if (r > 0) {
|
|
r2 = send(hc->hc_fd, rbuf, r, MSG_DONTWAIT);
|
|
len = r - (r2 < 0 ? 0 : r2);
|
|
if (len) {
|
|
if (ssl->wbio_pos + len > ssl->wbio_size) {
|
|
ssl->wbio_buf = realloc(ssl->wbio_buf, ssl->wbio_pos + len);
|
|
ssl->wbio_size += len;
|
|
}
|
|
memcpy(ssl->wbio_buf + ssl->wbio_pos, rbuf + (len - r), len);
|
|
ssl->wbio_pos += len;
|
|
}
|
|
if (r2 < 0) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
|
|
http_client_poll_dir(hc, 0, 1);
|
|
errno = EAGAIN;
|
|
return r2;
|
|
}
|
|
return r2;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
http_client_ssl_recv( http_client_t *hc, void *buf, size_t len )
|
|
{
|
|
ssize_t r;
|
|
int e;
|
|
|
|
while (1) {
|
|
r = SSL_read(hc->hc_ssl->ssl, buf, len);
|
|
if (r > 0)
|
|
return r;
|
|
e = SSL_get_error(hc->hc_ssl->ssl, r);
|
|
if (e == SSL_ERROR_WANT_READ) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
r = http_client_ssl_read_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_WANT_WRITE) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_ZERO_RETURN) {
|
|
errno = EIO;
|
|
return -1;
|
|
} else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
|
|
errno = EBADF;
|
|
return -1;
|
|
} else if (e == SSL_ERROR_SSL) {
|
|
errno = EPERM;
|
|
return -1;
|
|
} else {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
http_client_ssl_send( http_client_t *hc, const void *buf, size_t len )
|
|
{
|
|
struct http_client_ssl *ssl = hc->hc_ssl;
|
|
ssize_t r, r2;
|
|
int e;
|
|
|
|
if (hc->hc_verify_peer < 0)
|
|
http_client_ssl_peer_verify(hc, 1); /* default method - verify */
|
|
while (1) {
|
|
if (!ssl->connected) {
|
|
r = SSL_connect(ssl->ssl);
|
|
if (r > 0) {
|
|
ssl->connected = 1;
|
|
if (hc->hc_verify_peer > 0) {
|
|
if (SSL_get_peer_certificate(ssl->ssl) == NULL ||
|
|
SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
|
|
tvhlog(LOG_ERR, "httpc", "SSL peer verification failed (%s:%i)%s %li",
|
|
hc->hc_host, hc->hc_port,
|
|
SSL_get_peer_certificate(ssl->ssl) ? " X509" : "",
|
|
SSL_get_verify_result(ssl->ssl));
|
|
errno = EPERM;
|
|
return -1;
|
|
}
|
|
}
|
|
goto write;
|
|
}
|
|
} else {
|
|
write:
|
|
r = SSL_write(ssl->ssl, buf, len);
|
|
}
|
|
if (r > 0) {
|
|
while (1) {
|
|
r2 = http_client_ssl_write_update(hc);
|
|
if (r2 < 0) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
|
|
break;
|
|
return r2;
|
|
}
|
|
if (r2 == 0)
|
|
break;
|
|
}
|
|
return r;
|
|
}
|
|
e = SSL_get_error(ssl->ssl, r);
|
|
ERR_print_errors_fp(stdout);
|
|
if (e == SSL_ERROR_WANT_READ) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
r = http_client_ssl_read_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_WANT_WRITE) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
|
|
errno = EBADF;
|
|
return -1;
|
|
} else if (e == SSL_ERROR_SSL) {
|
|
errno = EPERM;
|
|
return -1;
|
|
} else {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
http_client_ssl_shutdown( http_client_t *hc )
|
|
{
|
|
ssize_t r;
|
|
int e;
|
|
|
|
while (1) {
|
|
r = SSL_shutdown(hc->hc_ssl->ssl);
|
|
if (r > 0) {
|
|
/* everything done, bail-out completely */
|
|
http_client_shutdown(hc, 1, 0);
|
|
return r;
|
|
}
|
|
e = SSL_get_error(hc->hc_ssl->ssl, r);
|
|
if (e == SSL_ERROR_WANT_READ) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
r = http_client_ssl_read_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_WANT_WRITE) {
|
|
r = http_client_ssl_write_update(hc);
|
|
if (r < 0)
|
|
return r;
|
|
} else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
|
|
errno = EBADF;
|
|
return -1;
|
|
} else if (r == SSL_ERROR_SSL) {
|
|
errno = EPERM;
|
|
return -1;
|
|
} else {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
http_client_send_partial( http_client_t *hc )
|
|
{
|
|
http_client_wcmd_t *wcmd;
|
|
ssize_t r;
|
|
int res = HTTP_CON_IDLE;
|
|
|
|
wcmd = TAILQ_FIRST(&hc->hc_wqueue);
|
|
while (wcmd != NULL) {
|
|
hc->hc_cmd = wcmd->wcmd;
|
|
hc->hc_rcseq = wcmd->wcseq;
|
|
if (hc->hc_einprogress) {
|
|
/* this seems like OSX specific issue */
|
|
/* send() in the EINPROGRESS state closes the file-descriptor */
|
|
int err = 0;
|
|
socklen_t errlen = sizeof(err);
|
|
getsockopt(hc->hc_fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen);
|
|
if (err == EINPROGRESS) {
|
|
r = err;
|
|
goto skip;
|
|
}
|
|
hc->hc_einprogress = 0;
|
|
}
|
|
if (hc->hc_ssl)
|
|
r = http_client_ssl_send(hc, wcmd->wbuf + wcmd->wpos,
|
|
wcmd->wsize - wcmd->wpos);
|
|
else
|
|
r = send(hc->hc_fd, wcmd->wbuf + wcmd->wpos,
|
|
wcmd->wsize - wcmd->wpos, MSG_DONTWAIT);
|
|
skip:
|
|
if (r < 0) {
|
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ||
|
|
errno == EINPROGRESS) {
|
|
http_client_direction(hc, 1);
|
|
return HTTP_CON_SENDING;
|
|
}
|
|
return http_client_flush(hc, -errno);
|
|
}
|
|
wcmd->wpos += r;
|
|
if (wcmd->wpos >= wcmd->wsize) {
|
|
http_client_cmd_destroy(hc, wcmd);
|
|
res = HTTP_CON_SENT;
|
|
wcmd = NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (wcmd == NULL) {
|
|
http_client_direction(hc, 0);
|
|
return res;
|
|
} else {
|
|
http_client_direction(hc, 1);
|
|
return HTTP_CON_SENDING;
|
|
}
|
|
}
|
|
|
|
int
|
|
http_client_send( http_client_t *hc, enum http_cmd cmd,
|
|
const char *path, const char *query,
|
|
http_arg_list_t *header, void *body, size_t body_size )
|
|
{
|
|
http_client_wcmd_t *wcmd = calloc(1, sizeof(*wcmd));
|
|
http_arg_t *h;
|
|
htsbuf_queue_t q;
|
|
const char *s;
|
|
|
|
if (hc->hc_shutdown) {
|
|
if (header)
|
|
http_arg_flush(header);
|
|
return -EIO;
|
|
}
|
|
|
|
wcmd->wcmd = cmd;
|
|
hc->hc_keepalive = 1;
|
|
|
|
htsbuf_queue_init(&q, 0);
|
|
s = http_cmd2str(cmd);
|
|
if (s == NULL) {
|
|
http_arg_flush(header);
|
|
return -EINVAL;
|
|
}
|
|
htsbuf_append(&q, s, strlen(s));
|
|
htsbuf_append(&q, " ", 1);
|
|
if (path == NULL || path[0] == '\0')
|
|
path = "/";
|
|
htsbuf_append(&q, path, strlen(path));
|
|
if (query && query[0] != '\0') {
|
|
htsbuf_append(&q, "?", 1);
|
|
htsbuf_append(&q, query, strlen(query));
|
|
}
|
|
htsbuf_append(&q, " ", 1);
|
|
s = http_ver2str(hc->hc_version);
|
|
if (s == NULL) {
|
|
htsbuf_queue_flush(&q);
|
|
http_arg_flush(header);
|
|
return -EINVAL;
|
|
}
|
|
htsbuf_append(&q, s, strlen(s));
|
|
htsbuf_append(&q, "\r\n", 2);
|
|
|
|
if (header) {
|
|
TAILQ_FOREACH(h, header, link) {
|
|
htsbuf_append(&q, h->key, strlen(h->key));
|
|
htsbuf_append(&q, ": ", 2);
|
|
htsbuf_append(&q, h->val, strlen(h->val));
|
|
htsbuf_append(&q, "\r\n", 2);
|
|
if (strcasecmp(h->key, "Connection") == 0 &&
|
|
strcasecmp(h->val, "close") == 0)
|
|
hc->hc_keepalive = 0;
|
|
}
|
|
http_arg_flush(header);
|
|
}
|
|
|
|
if (hc->hc_version == HTTP_VERSION_1_0)
|
|
hc->hc_keepalive = 0;
|
|
if (hc->hc_version == RTSP_VERSION_1_0) {
|
|
hc->hc_cseq = (hc->hc_cseq + 1) & 0x7fff;
|
|
htsbuf_qprintf(&q, "CSeq: %i\r\n", hc->hc_cseq);
|
|
wcmd->wcseq = hc->hc_cseq;
|
|
}
|
|
htsbuf_append(&q, "\r\n", 2);
|
|
if (body && body_size)
|
|
htsbuf_append(&q, body, body_size);
|
|
|
|
body_size = q.hq_size;
|
|
body = malloc(body_size);
|
|
htsbuf_read(&q, body, body_size);
|
|
|
|
#if ENABLE_TRACE
|
|
tvhtrace("httpc", "sending %s cmd", http_ver2str(hc->hc_version));
|
|
tvhlog_hexdump("httpc", body, body_size);
|
|
#endif
|
|
|
|
wcmd->wbuf = body;
|
|
wcmd->wsize = body_size;
|
|
|
|
TAILQ_INSERT_TAIL(&hc->hc_wqueue, wcmd, link);
|
|
|
|
hc->hc_ping_time = dispatch_clock;
|
|
|
|
return http_client_send_partial(hc);
|
|
}
|
|
|
|
static int
|
|
http_client_finish( http_client_t *hc )
|
|
{
|
|
int res;
|
|
|
|
#if ENABLE_TRACE
|
|
if (hc->hc_data) {
|
|
tvhtrace("httpc", "received %s data", http_ver2str(hc->hc_version));
|
|
tvhlog_hexdump("httpc", hc->hc_data, hc->hc_csize);
|
|
}
|
|
#endif
|
|
if (hc->hc_data_complete) {
|
|
res = hc->hc_data_complete(hc);
|
|
if (res < 0)
|
|
return http_client_flush(hc, res);
|
|
}
|
|
hc->hc_hsize = hc->hc_csize = 0;
|
|
if (hc->hc_version != RTSP_VERSION_1_0 &&
|
|
hc->hc_handle_location &&
|
|
(hc->hc_code == HTTP_STATUS_MOVED ||
|
|
hc->hc_code == HTTP_STATUS_FOUND ||
|
|
hc->hc_code == HTTP_STATUS_SEE_OTHER ||
|
|
hc->hc_code == HTTP_STATUS_NOT_MODIFIED)) {
|
|
const char *p = http_arg_get(&hc->hc_args, "Location");
|
|
if (p) {
|
|
hc->hc_location = strdup(p);
|
|
res = http_client_redirected(hc);
|
|
if (res < 0)
|
|
return http_client_flush(hc, res);
|
|
return HTTP_CON_RECEIVING;
|
|
}
|
|
}
|
|
if (TAILQ_FIRST(&hc->hc_wqueue) && hc->hc_code == HTTP_STATUS_OK)
|
|
return http_client_send_partial(hc);
|
|
if (!hc->hc_keepalive) {
|
|
http_client_shutdown(hc, 0, 0);
|
|
if (hc->hc_ssl) {
|
|
/* finish the shutdown I/O sequence, notify owner later */
|
|
errno = EAGAIN;
|
|
return HTTP_CON_RECEIVING;
|
|
}
|
|
}
|
|
return hc->hc_reconnected ? HTTP_CON_RECEIVING : HTTP_CON_DONE;
|
|
}
|
|
|
|
static int
|
|
http_client_parse_arg( http_arg_list_t *list, const char *p )
|
|
{
|
|
char *d, *t;
|
|
|
|
d = strchr(p, ':');
|
|
if (d) {
|
|
*d++ = '\0';
|
|
while (*d && *d <= ' ')
|
|
d++;
|
|
t = d + strlen(d);
|
|
while (--t != d && *t <= ' ')
|
|
*t = '\0';
|
|
http_arg_set(list, p, d);
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
http_client_data_copy( http_client_t *hc, char *buf, size_t len )
|
|
{
|
|
int res;
|
|
|
|
if (hc->hc_data_received) {
|
|
res = hc->hc_data_received(hc, buf, len);
|
|
if (res < 0)
|
|
return res;
|
|
} else {
|
|
hc->hc_data = realloc(hc->hc_data, hc->hc_data_size + len + 1);
|
|
memcpy(hc->hc_data + hc->hc_data_size, buf, len);
|
|
hc->hc_data_size += len;
|
|
hc->hc_data[hc->hc_data_size] = '\0';
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
http_client_data_chunked( http_client_t *hc, char *buf, size_t len, int *end )
|
|
{
|
|
size_t old = len, l, l2;
|
|
char *d, *s;
|
|
int res;
|
|
|
|
while (len > 0) {
|
|
if (hc->hc_chunk_size) {
|
|
s = hc->hc_chunk;
|
|
l = len;
|
|
if (hc->hc_chunk_pos + l > hc->hc_chunk_size)
|
|
l = hc->hc_chunk_size - hc->hc_chunk_pos;
|
|
memcpy(s + hc->hc_chunk_pos, buf, l);
|
|
hc->hc_chunk_pos += l;
|
|
buf += l;
|
|
len -= l;
|
|
if (hc->hc_chunk_pos >= hc->hc_chunk_size) {
|
|
if (s[hc->hc_chunk_size - 2] != '\r' &&
|
|
s[hc->hc_chunk_size - 1] != '\n')
|
|
return -EIO;
|
|
res = http_client_data_copy(hc, hc->hc_chunk, hc->hc_chunk_size - 2);
|
|
if (res < 0)
|
|
return res;
|
|
hc->hc_chunk_size = hc->hc_chunk_pos = 0;
|
|
}
|
|
continue;
|
|
}
|
|
l = 0;
|
|
if (hc->hc_chunk_csize) {
|
|
s = d = hc->hc_chunk;
|
|
if (buf[0] == '\n' && s[hc->hc_chunk_csize-1] == '\r')
|
|
l = 1;
|
|
else if (len > 1 && buf[0] == '\r' && buf[1] == '\n')
|
|
l = 2;
|
|
} else {
|
|
d = strstr(s = buf, "\r\n");
|
|
if (d) {
|
|
*d = '\0';
|
|
l = (d + 2) - s;
|
|
}
|
|
}
|
|
if (l) {
|
|
hc->hc_chunk_csize = 0;
|
|
if (hc->hc_chunk_trails) {
|
|
buf += l;
|
|
len -= l;
|
|
if (s[0] == '\0') {
|
|
*end = 1;
|
|
return old - len;
|
|
}
|
|
res = http_client_parse_arg(&hc->hc_args, s);
|
|
if (res < 0)
|
|
return res;
|
|
continue;
|
|
}
|
|
if (s[0] == '0' && s[1] == '\0')
|
|
hc->hc_chunk_trails = 1;
|
|
else {
|
|
hc->hc_chunk_size = strtoll(s, NULL, 16);
|
|
if (hc->hc_chunk_size == 0)
|
|
return -EIO;
|
|
if (hc->hc_chunk_size > 256*1024)
|
|
return -EMSGSIZE;
|
|
hc->hc_chunk_size += 2; /* CR-LF */
|
|
if (hc->hc_chunk_alloc < hc->hc_chunk_size) {
|
|
hc->hc_chunk = realloc(hc->hc_chunk, hc->hc_chunk_size + 1);
|
|
hc->hc_chunk[hc->hc_chunk_size] = '\0';
|
|
hc->hc_chunk_alloc = hc->hc_chunk_size;
|
|
}
|
|
}
|
|
buf += l;
|
|
len -= l;
|
|
} else {
|
|
l2 = hc->hc_chunk_csize + len;
|
|
if (l2 > hc->hc_chunk_alloc) {
|
|
hc->hc_chunk = realloc(hc->hc_chunk, l2 + 1);
|
|
hc->hc_chunk[l2] = '\0';
|
|
hc->hc_chunk_alloc = l2;
|
|
}
|
|
memcpy(hc->hc_chunk + hc->hc_chunk_csize, buf, len);
|
|
hc->hc_chunk_csize += len;
|
|
buf += len;
|
|
len -= len;
|
|
}
|
|
}
|
|
return old;
|
|
}
|
|
|
|
static int
|
|
http_client_data_received( http_client_t *hc, char *buf, ssize_t len, int hdr )
|
|
{
|
|
ssize_t l, l2, csize;
|
|
int res, end = 0;
|
|
|
|
buf[len] = '\0';
|
|
|
|
if (len == 0) {
|
|
if (hc->hc_csize == -1)
|
|
return 1;
|
|
if (!hdr && hc->hc_rpos >= hc->hc_csize)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
csize = hc->hc_csize == (size_t) -1 ? 0 : hc->hc_csize;
|
|
l = len;
|
|
if (hc->hc_csize && hc->hc_csize != (size_t) -1 && hc->hc_rpos > csize) {
|
|
l2 = hc->hc_rpos - csize;
|
|
if (l2 < l)
|
|
l = l2;
|
|
}
|
|
if (l) {
|
|
if (hc->hc_chunked) {
|
|
l = http_client_data_chunked(hc, buf, l, &end);
|
|
if (l < 0)
|
|
return l;
|
|
} else {
|
|
res = http_client_data_copy(hc, buf, l);
|
|
if (res < 0)
|
|
return res;
|
|
}
|
|
}
|
|
hc->hc_rpos += l;
|
|
end |= hc->hc_csize && hc->hc_rpos >= hc->hc_csize;
|
|
if (l < len) {
|
|
l2 = len - l;
|
|
if (l2 > hc->hc_rsize)
|
|
hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize = l2 + 1);
|
|
memcpy(hc->hc_rbuf, buf + l, l2);
|
|
hc->hc_rbuf[l2] = '\0';
|
|
hc->hc_rpos = l2;
|
|
}
|
|
return end ? 1 : 0;
|
|
}
|
|
|
|
int
|
|
http_client_run( http_client_t *hc )
|
|
{
|
|
char *buf, *saveptr, *argv[3], *d, *p;
|
|
int ver;
|
|
ssize_t r;
|
|
size_t len;
|
|
int res;
|
|
|
|
if (hc == NULL)
|
|
return 0;
|
|
|
|
if (hc->hc_shutdown) {
|
|
if (hc->hc_ssl && hc->hc_ssl->shutdown) {
|
|
r = http_client_ssl_shutdown(hc);
|
|
if (r < 0) {
|
|
if (errno != EIO) {
|
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)
|
|
return HTTP_CON_SENDING;
|
|
return r;
|
|
}
|
|
}
|
|
if (r == 0)
|
|
return HTTP_CON_SENDING;
|
|
}
|
|
return hc->hc_result ? hc->hc_result : HTTP_CON_DONE;
|
|
}
|
|
|
|
if (hc->hc_sending) {
|
|
res = http_client_send_partial(hc);
|
|
if (res < 0 || res == HTTP_CON_SENDING)
|
|
return res;
|
|
}
|
|
|
|
buf = alloca(hc->hc_io_size);
|
|
|
|
if (!hc->hc_in_data && hc->hc_rpos > 3 &&
|
|
(d = strstr(hc->hc_rbuf, "\r\n\r\n")) != NULL)
|
|
goto header;
|
|
|
|
retry:
|
|
if (hc->hc_ssl)
|
|
r = http_client_ssl_recv(hc, buf, hc->hc_io_size);
|
|
else
|
|
r = recv(hc->hc_fd, buf, hc->hc_io_size, MSG_DONTWAIT);
|
|
if (r == 0) {
|
|
if (hc->hc_in_data && !hc->hc_keepalive)
|
|
return http_client_finish(hc);
|
|
return http_client_flush(hc, -EIO);
|
|
}
|
|
if (r < 0) {
|
|
if (errno == EIO && hc->hc_in_data && !hc->hc_keepalive)
|
|
return http_client_finish(hc);
|
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
|
|
return HTTP_CON_RECEIVING;
|
|
return http_client_flush(hc, -errno);
|
|
}
|
|
#if ENABLE_TRACE
|
|
if (r > 0) {
|
|
tvhtrace("httpc", "received %s answer", http_ver2str(hc->hc_version));
|
|
tvhlog_hexdump("httpc", buf, r);
|
|
}
|
|
#endif
|
|
|
|
if (hc->hc_in_data) {
|
|
res = http_client_data_received(hc, buf, r, 0);
|
|
if (res < 0)
|
|
return http_client_flush(hc, res);
|
|
if (res > 0)
|
|
return http_client_finish(hc);
|
|
if (hc->hc_data_limit && r + hc->hc_rsize >= hc->hc_data_limit)
|
|
return http_client_flush(hc, -EOVERFLOW);
|
|
goto retry;
|
|
}
|
|
|
|
if (hc->hc_rsize < r + hc->hc_rpos) {
|
|
if (hc->hc_rsize + r > 16*1024)
|
|
return http_client_flush(hc, -EMSGSIZE);
|
|
hc->hc_rsize += r;
|
|
hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize + 1);
|
|
}
|
|
memcpy(hc->hc_rbuf + hc->hc_rpos, buf, r);
|
|
hc->hc_rpos += r;
|
|
hc->hc_rbuf[hc->hc_rpos] = '\0';
|
|
|
|
next_header:
|
|
if (hc->hc_rpos < 3)
|
|
return HTTP_CON_RECEIVING;
|
|
if ((d = strstr(hc->hc_rbuf, "\r\n\r\n")) == NULL)
|
|
return HTTP_CON_RECEIVING;
|
|
|
|
header:
|
|
*d = '\0';
|
|
len = hc->hc_rpos;
|
|
hc->hc_reconnected = 0;
|
|
http_client_clear_state(hc);
|
|
hc->hc_rpos = len;
|
|
hc->hc_hsize = d - hc->hc_rbuf + 4;
|
|
p = strtok_r(hc->hc_rbuf, "\r\n", &saveptr);
|
|
if (p == NULL)
|
|
return http_client_flush(hc, -EINVAL);
|
|
tvhtrace("httpc", "%s answer '%s'", http_ver2str(hc->hc_version), p);
|
|
if (http_tokenize(p, argv, 3, -1) != 3)
|
|
return http_client_flush(hc, -EINVAL);
|
|
if ((ver = http_str2ver(argv[0])) < 0)
|
|
return http_client_flush(hc, -EINVAL);
|
|
if (ver != hc->hc_version) {
|
|
/* 1.1 -> 1.0 transition allowed */
|
|
if (hc->hc_version == HTTP_VERSION_1_1 && ver == HTTP_VERSION_1_0)
|
|
hc->hc_version = ver;
|
|
else
|
|
return http_client_flush(hc, -EINVAL);
|
|
}
|
|
if ((hc->hc_code = atoi(argv[1])) < 200)
|
|
return http_client_flush(hc, -EINVAL);
|
|
while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) {
|
|
res = http_client_parse_arg(&hc->hc_args, p);
|
|
if (res < 0)
|
|
return http_client_flush(hc, -EINVAL);
|
|
}
|
|
p = http_arg_get(&hc->hc_args, "Content-Length");
|
|
if (p) {
|
|
hc->hc_csize = atoll(p);
|
|
if (hc->hc_csize == 0)
|
|
hc->hc_csize = -1;
|
|
}
|
|
p = http_arg_get(&hc->hc_args, "Connection");
|
|
if (p && ver != RTSP_VERSION_1_0) {
|
|
if (strcasecmp(p, "close") == 0)
|
|
hc->hc_keepalive = 0;
|
|
else if (hc->hc_keepalive && strcasecmp(p, "keep-alive"))
|
|
return http_client_flush(hc, -EINVAL);
|
|
else if (!hc->hc_keepalive && strcasecmp(p, "close"))
|
|
return http_client_flush(hc, -EINVAL);
|
|
}
|
|
if (ver == RTSP_VERSION_1_0) {
|
|
p = http_arg_get(&hc->hc_args, "CSeq");
|
|
if (p == NULL || hc->hc_rcseq != atoi(p))
|
|
return http_client_flush(hc, -EINVAL);
|
|
}
|
|
p = http_arg_get(&hc->hc_args, "Transfer-Encoding");
|
|
if (p)
|
|
hc->hc_chunked = strcasecmp(p, "chunked") == 0;
|
|
if (hc->hc_hdr_received) {
|
|
res = hc->hc_hdr_received(hc);
|
|
if (res < 0)
|
|
return http_client_flush(hc, res);
|
|
}
|
|
hc->hc_rpos -= hc->hc_hsize;
|
|
len = hc->hc_rpos;
|
|
if (hc->hc_code == HTTP_STATUS_CONTINUE) {
|
|
memmove(hc->hc_rbuf, hc->hc_rbuf + hc->hc_hsize, len);
|
|
goto next_header;
|
|
}
|
|
hc->hc_rpos = 0;
|
|
if (hc->hc_version == RTSP_VERSION_1_0 && !hc->hc_csize) {
|
|
hc->hc_csize = -1;
|
|
hc->hc_in_data = 0;
|
|
} else {
|
|
hc->hc_in_data = 1;
|
|
}
|
|
res = http_client_data_received(hc, hc->hc_rbuf + hc->hc_hsize, len, 1);
|
|
if (res < 0)
|
|
return http_client_flush(hc, res);
|
|
if (res > 0)
|
|
return http_client_finish(hc);
|
|
goto retry;
|
|
}
|
|
|
|
/*
|
|
* Redirected
|
|
*/
|
|
static void
|
|
http_client_basic_args ( http_arg_list_t *h, const url_t *url, int keepalive )
|
|
{
|
|
char buf[64];
|
|
|
|
http_arg_init(h);
|
|
snprintf(buf, sizeof(buf), "%s:%u", url->host,
|
|
http_port(url->scheme, url->port));
|
|
http_arg_set(h, "Host", buf);
|
|
snprintf(buf, sizeof(buf), "TVHeadend/%s", tvheadend_version);
|
|
http_arg_set(h, "User-Agent", buf);
|
|
if (!keepalive)
|
|
http_arg_set(h, "Connection", "close");
|
|
if (url->user && url->user[0] && url->pass && url->pass[0]) {
|
|
#define BASIC "Basic "
|
|
size_t plen = strlen(url->pass);
|
|
size_t ulen = strlen(url->user);
|
|
size_t len = BASE64_SIZE(plen + ulen + 1) + 1;
|
|
char *buf = alloca(ulen + 1 + plen + 1);
|
|
char *cbuf = alloca(len + sizeof(BASIC) + 1);
|
|
strcpy(buf, url->user);
|
|
strcat(buf, ":");
|
|
strcat(buf, url->pass);
|
|
strcpy(cbuf, BASIC);
|
|
base64_encode(cbuf + sizeof(BASIC) - 1, len,
|
|
(uint8_t *)buf, ulen + 1 + plen);
|
|
http_arg_set(h, "Authorization", cbuf);
|
|
#undef BASIC
|
|
}
|
|
}
|
|
|
|
static int
|
|
http_client_redirected ( http_client_t *hc )
|
|
{
|
|
char *location, *location2;
|
|
http_arg_list_t h;
|
|
tvhpoll_t *efd;
|
|
url_t u;
|
|
int r;
|
|
|
|
if (++hc->hc_redirects > 10)
|
|
return -ELOOP;
|
|
|
|
location = hc->hc_location;
|
|
location2 = hc->hc_location = NULL;
|
|
|
|
if (location[0] == '\0' || location[0] == '/') {
|
|
size_t size2 = strlen(hc->hc_scheme) + 3 + strlen(hc->hc_host) +
|
|
12 + strlen(location) + 1;
|
|
location2 = alloca(size2);
|
|
snprintf(location2, size2, "%s://%s:%i%s",
|
|
hc->hc_scheme, hc->hc_host, hc->hc_port, location);
|
|
}
|
|
|
|
memset(&u, 0, sizeof(u));
|
|
if (urlparse(location2 ? location2 : location, &u)) {
|
|
tvherror("httpc", "redirection - cannot parse url '%s'",
|
|
location2 ? location2 : location);
|
|
free(location);
|
|
return -EIO;
|
|
}
|
|
free(location);
|
|
|
|
if (strcmp(u.scheme, hc->hc_scheme) ||
|
|
strcmp(u.host, hc->hc_host) ||
|
|
http_port(u.scheme, u.port) != hc->hc_port ||
|
|
!hc->hc_keepalive) {
|
|
efd = hc->hc_efd;
|
|
http_client_shutdown(hc, 1, 1);
|
|
r = http_client_reconnect(hc, hc->hc_version,
|
|
u.scheme, u.host, u.port);
|
|
if (r < 0) {
|
|
urlreset(&u);
|
|
return r;
|
|
}
|
|
r = hc->hc_verify_peer;
|
|
hc->hc_verify_peer = -1;
|
|
http_client_ssl_peer_verify(hc, r);
|
|
hc->hc_efd = efd;
|
|
}
|
|
|
|
http_client_flush(hc, 0);
|
|
|
|
http_client_basic_args(&h, &u, hc->hc_keepalive);
|
|
hc->hc_reconnected = 1;
|
|
hc->hc_shutdown = 0;
|
|
hc->hc_pevents = 0;
|
|
|
|
r = http_client_send(hc, hc->hc_cmd, u.path, u.query, &h, NULL, 0);
|
|
if (r < 0) {
|
|
urlreset(&u);
|
|
return r;
|
|
}
|
|
|
|
hc->hc_reconnected = 1;
|
|
urlreset(&u);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
http_client_simple( http_client_t *hc, const url_t *url )
|
|
{
|
|
http_arg_list_t h;
|
|
|
|
http_client_basic_args(&h, url, 0);
|
|
return http_client_send(hc, HTTP_CMD_GET, url->path, url->query,
|
|
&h, NULL, 0);
|
|
}
|
|
|
|
void
|
|
http_client_ssl_peer_verify( http_client_t *hc, int verify )
|
|
{
|
|
struct http_client_ssl *ssl;
|
|
|
|
if (hc->hc_verify_peer < 0) {
|
|
hc->hc_verify_peer = verify ? 1 : 0;
|
|
if ((ssl = hc->hc_ssl) != NULL) {
|
|
if (!SSL_CTX_set_default_verify_paths(ssl->ctx))
|
|
tvherror("httpc", "SSL - unable to load CA certificates for verification");
|
|
SSL_CTX_set_verify_depth(ssl->ctx, 1);
|
|
SSL_CTX_set_verify(ssl->ctx,
|
|
hc->hc_verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
|
|
NULL);
|
|
}
|
|
} else {
|
|
tvherror("httpc", "SSL peer verification method must be set only once");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Data thread
|
|
*/
|
|
static void *
|
|
http_client_thread ( void *p )
|
|
{
|
|
int n;
|
|
tvhpoll_event_t ev;
|
|
http_client_t *hc;
|
|
char c;
|
|
|
|
while (http_running) {
|
|
n = tvhpoll_wait(http_poll, &ev, 1, -1);
|
|
if (n < 0) {
|
|
if (http_running &&
|
|
errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK)
|
|
tvherror("httpc", "tvhpoll_wait() error");
|
|
} else if (n > 0) {
|
|
if (&http_pipe == ev.data.ptr) {
|
|
if (read(http_pipe.rd, &c, 1) == 1) {
|
|
/* end-of-task */
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
pthread_mutex_lock(&http_lock);
|
|
TAILQ_FOREACH(hc, &http_clients, hc_link)
|
|
if (hc == ev.data.ptr)
|
|
break;
|
|
if (hc == NULL || hc->hc_shutdown_wait) {
|
|
if (hc->hc_shutdown_wait) {
|
|
pthread_cond_broadcast(&http_cond);
|
|
/* Disable the poll looping for this moment */
|
|
http_client_poll_dir(hc, 0, 0);
|
|
}
|
|
pthread_mutex_unlock(&http_lock);
|
|
continue;
|
|
}
|
|
hc->hc_running = 1;
|
|
pthread_mutex_unlock(&http_lock);
|
|
http_client_run(hc);
|
|
pthread_mutex_lock(&http_lock);
|
|
hc->hc_running = 0;
|
|
if (hc->hc_shutdown_wait)
|
|
pthread_cond_broadcast(&http_cond);
|
|
pthread_mutex_unlock(&http_lock);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
http_client_ssl_free( http_client_t *hc )
|
|
{
|
|
struct http_client_ssl *ssl;
|
|
|
|
if ((ssl = hc->hc_ssl) != NULL) {
|
|
free(ssl->rbio_buf);
|
|
free(ssl->wbio_buf);
|
|
SSL_free(ssl->ssl);
|
|
SSL_CTX_free(ssl->ctx);
|
|
free(ssl);
|
|
hc->hc_ssl = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup a connection (async)
|
|
*/
|
|
static int
|
|
http_client_reconnect
|
|
( http_client_t *hc, http_ver_t ver, const char *scheme,
|
|
const char *host, int port )
|
|
{
|
|
struct http_client_ssl *ssl;
|
|
char errbuf[256];
|
|
|
|
free(hc->hc_scheme);
|
|
free(hc->hc_host);
|
|
|
|
port = http_port(scheme, port);
|
|
hc->hc_pevents = 0;
|
|
hc->hc_version = ver;
|
|
hc->hc_scheme = strdup(scheme);
|
|
hc->hc_host = strdup(host);
|
|
hc->hc_port = port;
|
|
if (port < 0)
|
|
return -EINVAL;
|
|
hc->hc_fd = tcp_connect(host, port, hc->hc_bindaddr, errbuf, sizeof(errbuf), -1);
|
|
if (hc->hc_fd < 0) {
|
|
tvhlog(LOG_ERR, "httpc", "Unable to connect to %s:%i - %s", host, port, errbuf);
|
|
return -EINVAL;
|
|
}
|
|
hc->hc_einprogress = 1;
|
|
tvhtrace("httpc", "Connected to %s:%i", host, port);
|
|
http_client_ssl_free(hc);
|
|
if (strcasecmp(scheme, "https") == 0 || strcasecmp(scheme, "rtsps") == 0) {
|
|
ssl = calloc(1, sizeof(*ssl));
|
|
hc->hc_ssl = ssl;
|
|
ssl->ctx = SSL_CTX_new(SSLv23_client_method());
|
|
if (ssl->ctx == NULL) {
|
|
tvhlog(LOG_ERR, "httpc", "Unable to get SSL_CTX");
|
|
goto err1;
|
|
}
|
|
/* do not use SSLv2 */
|
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION);
|
|
/* adjust cipher list */
|
|
if (SSL_CTX_set_cipher_list(ssl->ctx, "HIGH:MEDIUM") != 1) {
|
|
tvhlog(LOG_ERR, "httpc", "Unable to adjust SSL cipher list");
|
|
goto err2;
|
|
}
|
|
ssl->rbio = BIO_new(BIO_s_mem());
|
|
ssl->wbio = BIO_new(BIO_s_mem());
|
|
ssl->ssl = SSL_new(ssl->ctx);
|
|
if (ssl->ssl == NULL || ssl->rbio == NULL || ssl->wbio == NULL) {
|
|
tvhlog(LOG_ERR, "httpc", "Unable to get SSL handle");
|
|
goto err3;
|
|
}
|
|
SSL_set_bio(ssl->ssl, ssl->rbio, ssl->wbio);
|
|
if (!SSL_set_tlsext_host_name(ssl->ssl, host)) {
|
|
tvhlog(LOG_ERR, "httpc", "Unable to set SSL hostname");
|
|
goto err4;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err4:
|
|
SSL_free(ssl->ssl);
|
|
err3:
|
|
BIO_free(ssl->rbio);
|
|
BIO_free(ssl->wbio);
|
|
err2:
|
|
SSL_CTX_free(ssl->ctx);
|
|
err1:
|
|
close(hc->hc_fd);
|
|
free(ssl);
|
|
return -EINVAL;
|
|
}
|
|
|
|
http_client_t *
|
|
http_client_connect
|
|
( void *aux, http_ver_t ver, const char *scheme,
|
|
const char *host, int port, const char *bindaddr )
|
|
{
|
|
http_client_t *hc;
|
|
|
|
hc = calloc(1, sizeof(http_client_t));
|
|
hc->hc_aux = aux;
|
|
hc->hc_io_size = 1024;
|
|
hc->hc_rtsp_stream_id = -1;
|
|
hc->hc_verify_peer = -1;
|
|
hc->hc_bindaddr = bindaddr ? strdup(bindaddr) : NULL;
|
|
|
|
TAILQ_INIT(&hc->hc_args);
|
|
TAILQ_INIT(&hc->hc_wqueue);
|
|
|
|
if (http_client_reconnect(hc, ver, scheme, host, port) < 0) {
|
|
free(hc);
|
|
return NULL;
|
|
}
|
|
|
|
return hc;
|
|
}
|
|
|
|
/*
|
|
* Register to the another thread
|
|
*/
|
|
void
|
|
http_client_register( http_client_t *hc )
|
|
{
|
|
assert(hc->hc_data_received || hc->hc_conn_closed);
|
|
assert(hc->hc_efd == NULL);
|
|
|
|
pthread_mutex_lock(&http_lock);
|
|
|
|
TAILQ_INSERT_TAIL(&http_clients, hc, hc_link);
|
|
|
|
hc->hc_efd = http_poll;
|
|
|
|
pthread_mutex_unlock(&http_lock);
|
|
}
|
|
|
|
/*
|
|
* Cancel
|
|
*
|
|
* This function is not allowed to be called inside the callbacks for
|
|
* registered clients to the http_client_thread .
|
|
*/
|
|
void
|
|
http_client_close ( http_client_t *hc )
|
|
{
|
|
http_client_wcmd_t *wcmd;
|
|
|
|
if (hc == NULL)
|
|
return;
|
|
|
|
if (hc->hc_efd == http_poll) { /* http_client_thread */
|
|
pthread_mutex_lock(&http_lock);
|
|
hc->hc_shutdown_wait = 1;
|
|
while (hc->hc_running)
|
|
pthread_cond_wait(&http_cond, &http_lock);
|
|
pthread_mutex_unlock(&http_lock);
|
|
}
|
|
http_client_shutdown(hc, 1, 0);
|
|
http_client_flush(hc, 0);
|
|
while ((wcmd = TAILQ_FIRST(&hc->hc_wqueue)) != NULL)
|
|
http_client_cmd_destroy(hc, wcmd);
|
|
http_client_ssl_free(hc);
|
|
rtsp_clear_session(hc);
|
|
free(hc->hc_location);
|
|
free(hc->hc_rbuf);
|
|
free(hc->hc_data);
|
|
free(hc->hc_host);
|
|
free(hc->hc_scheme);
|
|
free(hc->hc_bindaddr);
|
|
free(hc);
|
|
}
|
|
|
|
/*
|
|
* Initialise subsystem
|
|
*/
|
|
pthread_t http_client_tid;
|
|
|
|
void
|
|
http_client_init ( void )
|
|
{
|
|
tvhpoll_event_t ev;
|
|
|
|
/* Setup list */
|
|
pthread_mutex_init(&http_lock, NULL);
|
|
pthread_cond_init(&http_cond, NULL);
|
|
TAILQ_INIT(&http_clients);
|
|
|
|
/* Setup pipe */
|
|
tvh_pipe(O_NONBLOCK, &http_pipe);
|
|
|
|
/* Setup poll */
|
|
http_poll = tvhpoll_create(10);
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.fd = http_pipe.rd;
|
|
ev.events = TVHPOLL_IN;
|
|
ev.data.ptr = &http_pipe;
|
|
tvhpoll_add(http_poll, &ev, 1);
|
|
|
|
/* Setup thread */
|
|
http_running = 1;
|
|
tvhthread_create(&http_client_tid, NULL, http_client_thread, NULL, 0);
|
|
#if HTTPCLIENT_TESTSUITE
|
|
http_client_testsuite_run();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
http_client_done ( void )
|
|
{
|
|
http_running = 0;
|
|
tvh_write(http_pipe.wr, "", 1);
|
|
pthread_join(http_client_tid, NULL);
|
|
assert(TAILQ_FIRST(&http_clients) == NULL);
|
|
tvh_pipe_close(&http_pipe);
|
|
tvhpoll_destroy(http_poll);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* TESTSUITE
|
|
*
|
|
*/
|
|
|
|
#if HTTPCLIENT_TESTSUITE
|
|
|
|
static int
|
|
http_client_testsuite_hdr_received( http_client_t *hc )
|
|
{
|
|
http_arg_t *ra;
|
|
|
|
fprintf(stderr, "HTTPCTS: Received header from %s:%i\n", hc->hc_host, hc->hc_port);
|
|
TAILQ_FOREACH(ra, &hc->hc_args, link)
|
|
fprintf(stderr, " %s: %s\n", ra->key, ra->val);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
http_client_testsuite_conn_closed( http_client_t *hc, int result )
|
|
{
|
|
fprintf(stderr, "HTTPCTS: Closed (result=%i - %s)\n", result, strerror(result));
|
|
}
|
|
|
|
static int
|
|
http_client_testsuite_data_complete( http_client_t *hc )
|
|
{
|
|
fprintf(stderr, "HTTPCTS: Data Complete (code=%i, data=%p, data_size=%zi)\n",
|
|
hc->hc_code, hc->hc_data, hc->hc_data_size);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
http_client_testsuite_data_received( http_client_t *hc, void *data, size_t len )
|
|
{
|
|
fprintf(stderr, "HTTPCTS: Data received (len=%zi)\n", len);
|
|
/* check, if the data memory area is OK */
|
|
memset(data, 0xa5, len);
|
|
return 0;
|
|
}
|
|
|
|
static struct strtab HTTP_contab[] = {
|
|
{ "WAIT_REQUEST", HTTP_CON_WAIT_REQUEST },
|
|
{ "READ_HEADER", HTTP_CON_READ_HEADER },
|
|
{ "END", HTTP_CON_END },
|
|
{ "POST_DATA", HTTP_CON_POST_DATA },
|
|
{ "SENDING", HTTP_CON_SENDING },
|
|
{ "SENT", HTTP_CON_SENT },
|
|
{ "RECEIVING", HTTP_CON_RECEIVING },
|
|
{ "DONE", HTTP_CON_DONE },
|
|
};
|
|
|
|
static struct strtab ERRNO_tab[] = {
|
|
{ "EPERM", EPERM },
|
|
{ "ENOENT", ENOENT },
|
|
{ "ESRCH", ESRCH },
|
|
{ "EINTR", EINTR },
|
|
{ "EIO", EIO },
|
|
{ "ENXIO", ENXIO },
|
|
{ "E2BIG", E2BIG },
|
|
{ "ENOEXEC", ENOEXEC },
|
|
{ "EBADF", EBADF },
|
|
{ "ECHILD", ECHILD },
|
|
{ "EAGAIN", EAGAIN },
|
|
{ "ENOMEM", ENOMEM },
|
|
{ "EACCES", EACCES },
|
|
{ "EFAULT", EFAULT },
|
|
{ "ENOTBLK", ENOTBLK },
|
|
{ "EBUSY", EBUSY },
|
|
{ "EEXIST", EEXIST },
|
|
{ "EXDEV", EXDEV },
|
|
{ "ENODEV", ENODEV },
|
|
{ "ENOTDIR", ENOTDIR },
|
|
{ "EISDIR", EISDIR },
|
|
{ "EINVAL", EINVAL },
|
|
{ "ENFILE", ENFILE },
|
|
{ "EMFILE", EMFILE },
|
|
{ "ENOTTY", ENOTTY },
|
|
{ "ETXTBSY", ETXTBSY },
|
|
{ "EFBIG", EFBIG },
|
|
{ "ENOSPC", ENOSPC },
|
|
{ "ESPIPE", ESPIPE },
|
|
{ "EROFS", EROFS },
|
|
{ "EMLINK", EMLINK },
|
|
{ "EPIPE", EPIPE },
|
|
{ "EDOM", EDOM },
|
|
{ "ERANGE", ERANGE },
|
|
#ifdef __linux__
|
|
{ "EDEADLK", EDEADLK },
|
|
{ "ENAMETOOLONG", ENAMETOOLONG },
|
|
{ "ENOLCK", ENOLCK },
|
|
{ "ENOSYS", ENOSYS },
|
|
{ "ENOTEMPTY", ENOTEMPTY },
|
|
{ "ELOOP", ELOOP },
|
|
{ "EWOULDBLOCK", EWOULDBLOCK },
|
|
{ "ENOMSG", ENOMSG },
|
|
{ "EIDRM", EIDRM },
|
|
{ "ECHRNG", ECHRNG },
|
|
{ "EL2NSYNC", EL2NSYNC },
|
|
{ "EL3HLT", EL3HLT },
|
|
{ "EL3RST", EL3RST },
|
|
{ "ELNRNG", ELNRNG },
|
|
{ "EUNATCH", EUNATCH },
|
|
{ "ENOCSI", ENOCSI },
|
|
{ "EL2HLT", EL2HLT },
|
|
{ "EBADE", EBADE },
|
|
{ "EBADR", EBADR },
|
|
{ "EXFULL", EXFULL },
|
|
{ "ENOANO", ENOANO },
|
|
{ "EBADRQC", EBADRQC },
|
|
{ "EBADSLT", EBADSLT },
|
|
{ "EDEADLOCK", EDEADLOCK },
|
|
{ "EBFONT", EBFONT },
|
|
{ "ENOSTR", ENOSTR },
|
|
{ "ENODATA", ENODATA },
|
|
{ "ETIME", ETIME },
|
|
{ "ENOSR", ENOSR },
|
|
{ "ENONET", ENONET },
|
|
{ "ENOPKG", ENOPKG },
|
|
{ "EREMOTE", EREMOTE },
|
|
{ "ENOLINK", ENOLINK },
|
|
{ "EADV", EADV },
|
|
{ "ESRMNT", ESRMNT },
|
|
{ "ECOMM", ECOMM },
|
|
{ "EPROTO", EPROTO },
|
|
{ "EMULTIHOP", EMULTIHOP },
|
|
{ "EDOTDOT", EDOTDOT },
|
|
{ "EBADMSG", EBADMSG },
|
|
{ "EOVERFLOW", EOVERFLOW },
|
|
{ "ENOTUNIQ", ENOTUNIQ },
|
|
{ "EBADFD", EBADFD },
|
|
{ "EREMCHG", EREMCHG },
|
|
{ "ELIBACC", ELIBACC },
|
|
{ "ELIBBAD", ELIBBAD },
|
|
{ "ELIBSCN", ELIBSCN },
|
|
{ "ELIBMAX", ELIBMAX },
|
|
{ "ELIBEXEC", ELIBEXEC },
|
|
{ "EILSEQ", EILSEQ },
|
|
{ "ERESTART", ERESTART },
|
|
{ "ESTRPIPE", ESTRPIPE },
|
|
{ "EUSERS", EUSERS },
|
|
{ "ENOTSOCK", ENOTSOCK },
|
|
{ "EDESTADDRREQ", EDESTADDRREQ },
|
|
{ "EMSGSIZE", EMSGSIZE },
|
|
{ "EPROTOTYPE", EPROTOTYPE },
|
|
{ "ENOPROTOOPT", ENOPROTOOPT },
|
|
{ "EPROTONOSUPPORT", EPROTONOSUPPORT },
|
|
{ "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT },
|
|
{ "EOPNOTSUPP", EOPNOTSUPP },
|
|
{ "EPFNOSUPPORT", EPFNOSUPPORT },
|
|
{ "EAFNOSUPPORT", EAFNOSUPPORT },
|
|
{ "EADDRINUSE", EADDRINUSE },
|
|
{ "EADDRNOTAVAIL", EADDRNOTAVAIL },
|
|
{ "ENETDOWN", ENETDOWN },
|
|
{ "ENETUNREACH", ENETUNREACH },
|
|
{ "ENETRESET", ENETRESET },
|
|
{ "ECONNABORTED", ECONNABORTED },
|
|
{ "ECONNRESET", ECONNRESET },
|
|
{ "ENOBUFS", ENOBUFS },
|
|
{ "EISCONN", EISCONN },
|
|
{ "ENOTCONN", ENOTCONN },
|
|
{ "ESHUTDOWN", ESHUTDOWN },
|
|
{ "ETOOMANYREFS", ETOOMANYREFS },
|
|
{ "ETIMEDOUT", ETIMEDOUT },
|
|
{ "ECONNREFUSED", ECONNREFUSED },
|
|
{ "EHOSTDOWN", EHOSTDOWN },
|
|
{ "EHOSTUNREACH", EHOSTUNREACH },
|
|
{ "EALREADY", EALREADY },
|
|
{ "EINPROGRESS", EINPROGRESS },
|
|
{ "ESTALE", ESTALE },
|
|
{ "EUCLEAN", EUCLEAN },
|
|
{ "ENOTNAM", ENOTNAM },
|
|
{ "ENAVAIL", ENAVAIL },
|
|
{ "EISNAM", EISNAM },
|
|
{ "EREMOTEIO", EREMOTEIO },
|
|
{ "EDQUOT", EDQUOT },
|
|
{ "ENOMEDIUM", ENOMEDIUM },
|
|
{ "EMEDIUMTYPE", EMEDIUMTYPE },
|
|
{ "ECANCELED", ECANCELED },
|
|
{ "ENOKEY", ENOKEY },
|
|
{ "EKEYEXPIRED", EKEYEXPIRED },
|
|
{ "EKEYREVOKED", EKEYREVOKED },
|
|
{ "EKEYREJECTED", EKEYREJECTED },
|
|
{ "EOWNERDEAD", EOWNERDEAD },
|
|
{ "ENOTRECOVERABLE", ENOTRECOVERABLE },
|
|
#ifdef ERFKILL
|
|
{ "ERFKILL", ERFKILL },
|
|
#endif
|
|
#ifdef EHWPOISON
|
|
{ "EHWPOISON", EHWPOISON },
|
|
#endif
|
|
#endif /* __linux__ */
|
|
};
|
|
|
|
void
|
|
http_client_testsuite_run( void )
|
|
{
|
|
const char *path, *cs, *cs2;
|
|
char line[1024], *s;
|
|
http_arg_list_t args;
|
|
http_client_t *hc = NULL;
|
|
http_cmd_t cmd;
|
|
http_ver_t ver = HTTP_VERSION_1_1;
|
|
int data_transfer = 0, port = 0;
|
|
size_t data_limit = 0;
|
|
tvhpoll_event_t ev;
|
|
tvhpoll_t *efd;
|
|
url_t u1, u2;
|
|
FILE *fp;
|
|
int r, expected = HTTP_CON_DONE, expected_code = 200;
|
|
int handle_location = 0;
|
|
int peer_verify = 1;
|
|
|
|
path = getenv("TVHEADEND_HTTPC_TEST");
|
|
if (path == NULL)
|
|
path = TVHEADEND_DATADIR "/support/httpc-test.txt";
|
|
fp = fopen(path, "r");
|
|
if (fp == NULL) {
|
|
tvhlog(LOG_NOTICE, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
|
|
return;
|
|
}
|
|
memset(&u1, 0, sizeof(u1));
|
|
memset(&u2, 0, sizeof(u2));
|
|
http_arg_init(&args);
|
|
efd = tvhpoll_create(1);
|
|
while (fgets(line, sizeof(line), fp) != NULL && tvheadend_running) {
|
|
if (line[0] == '\0')
|
|
continue;
|
|
s = line + strlen(line) - 1;
|
|
while (*s < ' ' && s != line)
|
|
s--;
|
|
if (*s < ' ')
|
|
*s = '\0';
|
|
else
|
|
s[1] = '\0';
|
|
s = line;
|
|
while (*s && *s < ' ')
|
|
s++;
|
|
if (*s == '\0' || *s == '#')
|
|
continue;
|
|
if (strcmp(s, "Reset=1") == 0) {
|
|
ver = HTTP_VERSION_1_1;
|
|
urlreset(&u1);
|
|
urlreset(&u2);
|
|
http_client_close(hc);
|
|
hc = NULL;
|
|
data_transfer = 0;
|
|
data_limit = 0;
|
|
port = 0;
|
|
expected = HTTP_CON_DONE;
|
|
expected_code = 200;
|
|
handle_location = 0;
|
|
peer_verify = 1;
|
|
} else if (strcmp(s, "DataTransfer=all") == 0) {
|
|
data_transfer = 0;
|
|
} else if (strcmp(s, "DataTransfer=cont") == 0) {
|
|
data_transfer = 1;
|
|
} else if (strcmp(s, "HandleLocation=0") == 0) {
|
|
handle_location = 0;
|
|
} else if (strcmp(s, "HandleLocation=1") == 0) {
|
|
handle_location = 1;
|
|
} else if (strcmp(s, "SSLPeerVerify=0") == 0) {
|
|
peer_verify = 0;
|
|
} else if (strcmp(s, "SSLPeerVerify=1") == 0) {
|
|
peer_verify = 1;
|
|
} else if (strncmp(s, "DataLimit=", 10) == 0) {
|
|
data_limit = atoll(s + 10);
|
|
} else if (strncmp(s, "Port=", 5) == 0) {
|
|
port = atoi(s + 5);
|
|
} else if (strncmp(s, "ExpectedError=", 14) == 0) {
|
|
r = str2val(s + 14, HTTP_contab);
|
|
if (r < 0) {
|
|
r = str2val(s + 14, ERRNO_tab);
|
|
if (r < 0) {
|
|
fprintf(stderr, "HTTPCTS: Unknown error code '%s'\n", s + 14);
|
|
goto fatal;
|
|
} else {
|
|
r = -r;
|
|
}
|
|
}
|
|
expected = r;
|
|
} else if (strncmp(s, "ExpectedCode=", 13) == 0) {
|
|
expected_code = atoi(s + 13);
|
|
} else if (strncmp(s, "Header=", 7) == 0) {
|
|
r = http_client_parse_arg(&args, s + 7);
|
|
if (r < 0)
|
|
goto fatal;
|
|
} else if (strncmp(s, "Version=", 8) == 0) {
|
|
ver = http_str2ver(s + 8);
|
|
if (ver < 0)
|
|
goto fatal;
|
|
} else if (strncmp(s, "URL=", 4) == 0) {
|
|
urlreset(&u1);
|
|
if (urlparse(s + 4, &u1) < 0) {
|
|
fprintf(stderr, "HTTPCTS: Parse URL error for '%s'\n", s + 4);
|
|
goto fatal;
|
|
}
|
|
} else if (strncmp(s, "Command=", 8) == 0) {
|
|
if (strcmp(s + 8, "EXIT") == 0)
|
|
break;
|
|
if (u1.host == NULL || u1.host[0] == '\0') {
|
|
fprintf(stderr, "HTTPCTS: Define URL\n");
|
|
goto fatal;
|
|
}
|
|
cmd = http_str2cmd(s + 8);
|
|
if (cmd < 0)
|
|
goto fatal;
|
|
http_client_basic_args(&args, &u1, 1);
|
|
if (u2.host == NULL || u1.host == NULL || strcmp(u1.host, u2.host) ||
|
|
u2.port != u1.port || !hc->hc_keepalive) {
|
|
http_client_close(hc);
|
|
if (port)
|
|
u1.port = port;
|
|
hc = http_client_connect(NULL, ver, u1.scheme, u1.host, u1.port, NULL);
|
|
if (hc == NULL) {
|
|
fprintf(stderr, "HTTPCTS: Unable to connect to %s:%i (%s)\n", u1.host, u1.port, u1.scheme);
|
|
goto fatal;
|
|
} else {
|
|
fprintf(stderr, "HTTPCTS: Connected to %s:%i\n", hc->hc_host, hc->hc_port);
|
|
}
|
|
http_client_ssl_peer_verify(hc, peer_verify);
|
|
}
|
|
fprintf(stderr, "HTTPCTS Send: Cmd=%s Ver=%s Host=%s Path=%s\n",
|
|
http_cmd2str(cmd), http_ver2str(ver), http_arg_get(&args, "Host"), u1.path);
|
|
hc->hc_efd = efd;
|
|
hc->hc_handle_location = handle_location;
|
|
hc->hc_data_limit = data_limit;
|
|
hc->hc_hdr_received = http_client_testsuite_hdr_received;
|
|
hc->hc_data_complete = http_client_testsuite_data_complete;
|
|
hc->hc_conn_closed = http_client_testsuite_conn_closed;
|
|
if (data_transfer) {
|
|
hc->hc_data_received = http_client_testsuite_data_received;
|
|
} else {
|
|
hc->hc_data_received = NULL;
|
|
}
|
|
r = http_client_send(hc, cmd, u1.path, u1.query, &args, NULL, 0);
|
|
if (r < 0) {
|
|
fprintf(stderr, "HTTPCTS Send Failed %s\n", strerror(-r));
|
|
goto fatal;
|
|
}
|
|
while (tvheadend_running) {
|
|
fprintf(stderr, "HTTPCTS: Enter Poll\n");
|
|
r = tvhpoll_wait(efd, &ev, 1, -1);
|
|
fprintf(stderr, "HTTPCTS: Leave Poll: %i (%s)\n", r, r < 0 ? val2str(-r, ERRNO_tab) : "OK");
|
|
if (r < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
|
|
continue;
|
|
if (r < 0) {
|
|
fprintf(stderr, "HTTPCTS: Poll result: %s\n", strerror(-r));
|
|
goto fatal;
|
|
}
|
|
if (r != 1)
|
|
continue;
|
|
if (ev.data.ptr != hc) {
|
|
fprintf(stderr, "HTTPCTS: Poll returned a wrong value\n");
|
|
goto fatal;
|
|
}
|
|
r = http_client_run(hc);
|
|
cs = val2str(r, HTTP_contab);
|
|
if (cs == NULL)
|
|
cs = val2str(-r, ERRNO_tab);
|
|
cs2 = val2str(expected, HTTP_contab);
|
|
if (cs2 == NULL)
|
|
cs2 = val2str(-expected, ERRNO_tab);
|
|
fprintf(stderr, "HTTPCTS: Run Done, Result = %i (%s), Expected = %i (%s)\n", r, cs, expected, cs2);
|
|
if (r == expected) {
|
|
if (hc->hc_code != expected_code) {
|
|
fprintf(stderr, "HTTPCTS: HTTP Code Fail: Expected = %i Got = %i\n", expected_code, hc->hc_code);
|
|
goto fatal;
|
|
}
|
|
break;
|
|
}
|
|
if (r < 0)
|
|
goto fatal;
|
|
if (r == HTTP_CON_DONE)
|
|
goto fatal;
|
|
}
|
|
urlreset(&u2);
|
|
urlcopy(&u2, &u1);
|
|
urlreset(&u1);
|
|
http_client_clear_state(hc);
|
|
} else {
|
|
fprintf(stderr, "HTTPCTS: Wrong line '%s'\n", s);
|
|
}
|
|
}
|
|
urlreset(&u2);
|
|
urlreset(&u1);
|
|
http_client_close(hc);
|
|
tvhpoll_destroy(efd);
|
|
http_arg_flush(&args);
|
|
fclose(fp);
|
|
fprintf(stderr, "HTTPCTS Return To Main\n");
|
|
return;
|
|
fatal:
|
|
fprintf(stderr, "HTTPCTS Fatal Error\n");
|
|
exit(1);
|
|
}
|
|
|
|
#endif
|