]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add build-in http client
authorJaroslav Kysela <perex@perex.cz>
Mon, 14 Apr 2014 19:42:12 +0000 (21:42 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 5 May 2014 20:00:35 +0000 (22:00 +0200)
- supports SSL
- supports location redirections
- supports chunked data transfers
- supports HTTP/1.0 HTTP/1.1 and RTSP/1.0

13 files changed:
Makefile
src/http.c
src/http.h
src/http/http_client.c [deleted file]
src/httpc.c [new file with mode: 0644]
src/input/mpegts/iptv/iptv_http.c
src/input/mpegts/satip/satip.c
src/input/mpegts/satip/satip_rtsp.c
src/main.c
src/tcp.c
src/tvheadend.h
src/utils.c
support/httpc-test.txt [new file with mode: 0644]

index e0bb0f6c8733cab28950694e564aa238ed37abde..eaa63ad5f3ca2e383be9a3289993b3af5c9bb941 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -113,7 +113,7 @@ SRCS =  src/version.c \
        src/descrambler/descrambler.c \
        src/service_mapper.c \
        src/input.c \
-       src/http/http_client.c \
+       src/httpc.c \
        src/fsmonitor.c \
        src/cron.c \
 
index ddf7021346717d37fec536c5e0c9fcb2540230b9..5932d8a93fb214499b026125ad80f6108c2f72ff 100644 (file)
@@ -62,6 +62,31 @@ static struct strtab HTTP_versiontab[] = {
 
 static void http_parse_get_args(http_connection_t *hc, char *args);
 
+/**
+ *
+ */
+const char *
+http_cmd2str(int val)
+{
+  return val2str(val, HTTP_cmdtab);
+}
+
+int http_str2cmd(const char *str)
+{
+  return str2val(str, HTTP_cmdtab);
+}
+
+const char *
+http_ver2str(int val)
+{
+  return val2str(val, HTTP_versiontab);
+}
+
+int http_str2ver(const char *str)
+{
+  return str2val(str, HTTP_versiontab);
+}
+
 /**
  *
  */
@@ -547,7 +572,6 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
 
 
 
-
 /*
  * Delete all arguments associated with a connection
  */
@@ -582,7 +606,7 @@ http_arg_get(struct http_arg_list *list, const char *name)
  * Set an argument associated with a connection
  */
 void
-http_arg_set(struct http_arg_list *list, char *key, char *val)
+http_arg_set(struct http_arg_list *list, const char *key, const char *val)
 {
   http_arg_t *ra;
 
@@ -814,8 +838,8 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer,
   memset(&hc, 0, sizeof(http_connection_t));
   *opaque = &hc;
 
-  TAILQ_INIT(&hc.hc_args);
-  TAILQ_INIT(&hc.hc_req_args);
+  http_arg_init(&hc.hc_args);
+  http_arg_init(&hc.hc_req_args);
 
   hc.hc_fd = fd;
   hc.hc_peer = peer;
index eb3264ce293dcfadbc2f20376cd239fe37bde980..6abe0ea63b51283f5faea0771032214ed14c8998 100644 (file)
@@ -21,8 +21,9 @@
 
 #include "htsbuf.h"
 #include "url.h"
+#include "tvhpoll.h"
 
-TAILQ_HEAD(http_arg_list, http_arg);
+typedef TAILQ_HEAD(http_arg_list, http_arg) http_arg_list_t;
 
 typedef RB_HEAD(,http_arg) http_arg_tree_t;
 
@@ -33,13 +34,76 @@ typedef struct http_arg {
   char *val;
 } http_arg_t;
 
-#define HTTP_STATUS_OK           200
+#define HTTP_STATUS_CONTINUE        100
+#define HTTP_STATUS_PSWITCH         101
+#define HTTP_STATUS_OK              200
+#define HTTP_STATUS_CREATED         201
+#define HTTP_STATUS_ACCEPTED        202
+#define HTTP_STATUS_NON_AUTH_INFO   203
+#define HTTP_STATUS_NO_CONTENT      204
+#define HTTP_STATUS_RESET_CONTENT   205
 #define HTTP_STATUS_PARTIAL_CONTENT 206
-#define HTTP_STATUS_FOUND        302
-#define HTTP_STATUS_BAD_REQUEST  400
-#define HTTP_STATUS_UNAUTHORIZED 401
-#define HTTP_STATUS_NOT_FOUND    404
-
+#define HTTP_STATUS_MULTIPLE        300
+#define HTTP_STATUS_MOVED           301
+#define HTTP_STATUS_FOUND           302
+#define HTTP_STATUS_SEE_OTHER       303
+#define HTTP_STATUS_NOT_MODIFIED    304
+#define HTTP_STATUS_USE_PROXY       305
+#define HTTP_STATUS_TMP_REDIR       307
+#define HTTP_STATUS_BAD_REQUEST     400
+#define HTTP_STATUS_UNAUTHORIZED    401
+#define HTTP_STATUS_PAYMENT         402
+#define HTTP_STATUS_FORBIDDEN       403
+#define HTTP_STATUS_NOT_FOUND       404
+#define HTTP_STATUS_NOT_ALLOWED     405
+#define HTTP_STATUS_NOT_ACCEPTABLE  406
+#define HTTP_STATUS_PROXY_AUTH      407
+#define HTTP_STATUS_TIMEOUT         408
+#define HTTP_STATUS_CONFLICT        409
+#define HTTP_STATUS_GONE            410
+#define HTTP_STATUS_LENGTH          411
+#define HTTP_STATUS_PRECONDITION    412
+#define HTTP_STATUS_ENTITY_OVER     413
+#define HTTP_STATUS_URI_TOO_LONG    414
+#define HTTP_STATUS_UNSUPPORTED     415
+#define HTTP_STATUS_BAD_RANGE       417
+#define HTTP_STATUS_EXPECTATION     418
+#define HTTP_STATUS_INTERNAL        500
+#define HTTP_STATUS_NOT_IMPLEMENTED 501
+#define HTTP_STATUS_BAD_GATEWAY     502
+#define HTTP_STATUS_SERVICE         503
+#define HTTP_STATUS_GATEWAY_TIMEOUT 504
+#define HTTP_STATUS_HTTP_VERSION    505
+
+typedef enum http_state {
+  HTTP_CON_WAIT_REQUEST,
+  HTTP_CON_READ_HEADER,
+  HTTP_CON_END,
+  HTTP_CON_POST_DATA,
+  HTTP_CON_SENDING,
+  HTTP_CON_SENT,
+  HTTP_CON_RECEIVING,
+  HTTP_CON_DONE,
+  HTTP_CON_IDLE
+} http_state_t;
+
+typedef enum http_cmd {
+  HTTP_CMD_GET,
+  HTTP_CMD_HEAD,
+  HTTP_CMD_POST,
+  RTSP_CMD_DESCRIBE,
+  RTSP_CMD_OPTIONS,
+  RTSP_CMD_SETUP,
+  RTSP_CMD_TEARDOWN,
+  RTSP_CMD_PLAY,
+  RTSP_CMD_PAUSE,
+} http_cmd_t;
+
+typedef enum http_ver {
+  HTTP_VERSION_1_0,
+  HTTP_VERSION_1_1,
+  RTSP_VERSION_1_0,
+} http_ver_t;
 
 typedef struct http_connection {
   int hc_fd;
@@ -51,36 +115,15 @@ typedef struct http_connection {
   char *hc_url_orig;
   int hc_keep_alive;
 
-  htsbuf_queue_t hc_reply;
-
-  struct http_arg_list hc_args;
-
-  struct http_arg_list hc_req_args; /* Argumets from GET or POST request */
-
-  enum {
-    HTTP_CON_WAIT_REQUEST,
-    HTTP_CON_READ_HEADER,
-    HTTP_CON_END,
-    HTTP_CON_POST_DATA,
-  } hc_state;
-
-  enum {
-    HTTP_CMD_GET,
-    HTTP_CMD_HEAD,
-    HTTP_CMD_POST,
-    RTSP_CMD_DESCRIBE,
-    RTSP_CMD_OPTIONS,
-    RTSP_CMD_SETUP,
-    RTSP_CMD_TEARDOWN,
-    RTSP_CMD_PLAY,
-    RTSP_CMD_PAUSE,
-  } hc_cmd;
-
-  enum {
-    HTTP_VERSION_1_0,
-    HTTP_VERSION_1_1,
-    RTSP_VERSION_1_0,
-  } hc_version;
+  htsbuf_queue_t  hc_reply;
+
+  http_arg_list_t hc_args;
+
+  http_arg_list_t hc_req_args; /* Argumets from GET or POST request */
+
+  http_state_t    hc_state;
+  http_cmd_t      hc_cmd;
+  http_ver_t      hc_version;
 
   char *hc_username;
   char *hc_password;
@@ -99,11 +142,21 @@ typedef struct http_connection {
 } http_connection_t;
 
 
+const char *http_cmd2str(int val);
+int http_str2cmd(const char *str);
+const char *http_ver2str(int val);
+int http_str2ver(const char *str);
+
+static inline void http_arg_init(struct http_arg_list *list)
+{
+  TAILQ_INIT(list);
+}
+
 void http_arg_flush(struct http_arg_list *list);
 
 char *http_arg_get(struct http_arg_list *list, const char *name);
 
-void http_arg_set(struct http_arg_list *list, char *key, char *val);
+void http_arg_set(struct http_arg_list *list, const char *key, const char *val);
 
 int http_tokenize(char *buf, char **vec, int vecsize, int delimiter);
 
@@ -142,22 +195,104 @@ int http_access_verify(http_connection_t *hc, int mask);
 
 void http_deescape(char *s);
 
+/*
+ * HTTP/RTSP Client
+ */
+
 typedef struct http_client http_client_t;
 
-typedef void   (http_client_conn_cb) (void *p);
-typedef size_t (http_client_data_cb) (void *p, void *buf, size_t len);
-typedef void   (http_client_fail_cb) (void *p);
+typedef struct http_client_wcmd {
+
+  TAILQ_ENTRY(http_client_wcmd) link;
+
+  enum http_cmd wcmd;
+  int           wcseq;
+
+  void         *wbuf;
+  size_t        wpos;
+  size_t        wsize;
+} http_client_wcmd_t;
+
+struct http_client {
+
+  TAILQ_ENTRY(http_client) hc_link;
+
+  int          hc_fd;
+  char        *hc_scheme;
+  char        *hc_host;
+  int          hc_port;
+  tvhpoll_t   *hc_efd;
+  int          hc_pevents;
+
+  int          hc_code;
+  http_ver_t   hc_version;
+  http_cmd_t   hc_cmd;
+
+  struct http_arg_list hc_args; /* header */
+
+  void        *hc_aux;
+  size_t       hc_data_limit;
+  size_t       hc_io_size;
+  char        *hc_data;         /* data body */
+  size_t       hc_data_size;    /* data body size - result for caller */
+
+  time_t       hc_ping_time;    /* last issued command */
+
+  char        *hc_rbuf;         /* read buffer */
+  size_t       hc_rsize;        /* read buffer size */
+  size_t       hc_rpos;         /* read buffer position */
+  size_t       hc_hsize;        /* header size in bytes */
+  size_t       hc_csize;        /* contents size in bytes */
+  char        *hc_chunk;
+  size_t       hc_chunk_size;
+  size_t       hc_chunk_csize;
+  size_t       hc_chunk_alloc;
+  size_t       hc_chunk_pos;
+  char        *hc_location;
+  int          hc_redirects;
+  int          hc_result;
+  int          hc_shutdown:1;
+  int          hc_sending:1;
+  int          hc_reconnected:1;
+  int          hc_keepalive:1;
+  int          hc_in_data:1;
+  int          hc_chunked:1;
+  int          hc_chunk_trails:1;
+  int          hc_handle_location:1; /* handle the redirection (location) requests */
+
+  http_client_wcmd_t            *hc_wcmd;
+  TAILQ_HEAD(,http_client_wcmd)  hc_wqueue;
+
+  int          hc_cseq;         /* RTSP */
+  int          hc_rcseq;        /* RTSP - expected cseq */
+
+  struct http_client_ssl *hc_ssl; /* ssl internals */
+
+  /* callbacks */
+  int     (*hc_hdr_received) (http_client_t *hc);
+  int     (*hc_data_received)(http_client_t *hc, void *buf, size_t len);
+  int     (*hc_data_complete)(http_client_t *hc);
+  void    (*hc_conn_closed)  (http_client_t *hc, int err);
+};
 
 void http_client_init ( void );
 void http_client_done ( void );
+
 http_client_t*
-http_connect ( const url_t *url,
-               http_client_conn_cb conn_cb,
-               http_client_data_cb data_cb,
-               http_client_fail_cb fail_cb,
-               void *p );
-void http_close ( http_client_t *hc );
-
-void curl_done ( void );
+http_client_connect ( void *aux, http_ver_t ver,
+                      const char *scheme, const char *host, int port );
+void http_client_register ( http_client_t *hc );
+void http_client_close ( http_client_t *hc );
+
+int
+http_client_send( http_client_t *hc, http_cmd_t cmd,
+                  const char *path, const char *query,
+                  http_arg_list_t *header, void *body, size_t body_size );
+int
+http_client_simple( http_client_t *hc, const url_t *url);
+int
+http_client_clear_state( http_client_t *hc );
+int
+http_client_run( http_client_t *hc );
 
 #endif /* HTTP_H_ */
diff --git a/src/http/http_client.c b/src/http/http_client.c
deleted file mode 100644 (file)
index 5f7c56b..0000000
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- *  Tvheadend - HTTP client functions
- *
- *  Copyright (C) 2013 Adam Sutton
- *
- *  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 "tvhpoll.h"
-#include "redblack.h"
-#include "queue.h"
-#include "url.h"
-#include "http.h"
-
-#if ENABLE_CURL
-
-#include <curl/curl.h>
-#include <pthread.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <signal.h>
-#include <fcntl.h>
-
-
-/*
- * Client definition
- */
-struct http_client
-{
-  CURL *hc_curl;
-  int   hc_fd;
-  url_t hc_url;
-  int   hc_init;
-  int   hc_begin;
-
-  /* Callbacks */
-  http_client_conn_cb *hc_conn;
-  http_client_data_cb *hc_data;
-  http_client_fail_cb *hc_fail;
-  void                *hc_opaque;
-
-  TAILQ_ENTRY(http_client) hc_link;
-};
-
-/*
- * 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 CURLM                   *http_curlm;
-static th_pipe_t                http_pipe;
-
-/*
- * Disable
- */
-static void
-http_remove ( http_client_t *hc )
-{
-  tvhpoll_event_t ev;
-  ev.fd = hc->hc_fd;
-  
-  /* Remove */
-  curl_multi_remove_handle(http_curlm, hc->hc_curl);
-  tvhpoll_rem(http_poll, &ev, 1);
-  TAILQ_REMOVE(&http_clients, hc, hc_link);
-
-  /* Free CURL memory */
-  curl_easy_cleanup(hc->hc_curl);
-  hc->hc_curl = NULL;
-
-  urlreset(&hc->hc_url);
-}
-
-/*
- * New socket
- */
-static int
-http_curl_socket ( CURL *c, int fd, int a, void *u, void *s )
-{
-  http_client_t *hc;
-  tvhpoll_event_t ev = { 0 };
-  ev.fd = fd;
-
-  /* Find client */
-  TAILQ_FOREACH(hc, &http_clients, hc_link)
-    if (hc->hc_curl == c)
-      break;
-
-  /* Invalid */
-  if (!hc)
-    goto done;
-
-  /* Remove */
-  if (a == CURL_POLL_REMOVE) {
-    //http_remove(hc);
-    
-  /* Set */
-  } else if (a & CURL_POLL_INOUT) {
-    if (a & CURL_POLL_IN)
-      ev.events |= TVHPOLL_IN;
-    if (a & CURL_POLL_OUT)
-      ev.events |= TVHPOLL_OUT;
-    ev.data.fd  = fd;
-    ev.data.ptr = hc;
-    hc->hc_fd   = fd;
-    tvhpoll_add(http_poll, &ev, 1);
-  }
-
-  /* Done */
-done:
-  return 0;
-}
-
-/*
- * Data
- */
-static size_t
-http_curl_data ( void *buf, size_t len, size_t n, void *p )
-{
-  http_client_t *hc = p;
-  if (!hc->hc_begin && hc->hc_conn)
-    hc->hc_conn(hc->hc_opaque);
-  hc->hc_begin = 1;
-  len = hc->hc_data(hc->hc_opaque, buf, len * n);
-  return len;
-}
-
-/*
- * Data thread
- */
-static void *
-http_thread ( void *p )
-{
-  int n, run = 0;
-  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 (tvheadend_running)
-        tvherror("http_client", "tvhpoll_wait() error");
-    } else if (n > 0) {
-      if (&http_pipe == ev.data.ptr) {
-        if (read(http_pipe.rd, &c, 1) == 1) {
-          if (c == 'n') {
-            pthread_mutex_lock(&http_lock);
-            TAILQ_FOREACH(hc, &http_clients, hc_link) {
-              if (hc->hc_init == 0)
-                continue;
-              hc->hc_init = 0;
-              curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
-            }
-            pthread_mutex_unlock(&http_lock);
-          } else {
-            /* 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 && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT)))
-        curl_multi_socket_action(http_curlm, hc->hc_fd, 0, &run);
-      pthread_mutex_unlock(&http_lock);
-    }
-  }
-
-  return NULL;
-}
-
-/*
- * Setup a connection (async)
- */
-http_client_t *
-http_connect 
-  ( const url_t *url,
-    http_client_conn_cb conn_cb,
-    http_client_data_cb data_cb, 
-    http_client_fail_cb fail_cb, 
-    void *p )
-{
-  /* Setup structure */
-  http_client_t *hc = calloc(1, sizeof(http_client_t));
-  hc->hc_curl       = curl_easy_init();
-  urlcopy(&hc->hc_url, url);
-  hc->hc_conn       = conn_cb;
-  hc->hc_data       = data_cb;
-  hc->hc_fail       = fail_cb;
-  hc->hc_opaque     = p;
-  hc->hc_init       = 1;
-
-  /* Store */
-  pthread_mutex_lock(&http_lock);
-  TAILQ_INSERT_TAIL(&http_clients, hc, hc_link);
-
-  /* Setup connection */
-  curl_easy_setopt(hc->hc_curl, CURLOPT_URL, url->raw);
-  curl_easy_setopt(hc->hc_curl, CURLOPT_FOLLOWLOCATION, 1);
-  curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEFUNCTION, http_curl_data);
-  curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEDATA,     hc);
-  curl_multi_add_handle(http_curlm, hc->hc_curl);
-  pthread_mutex_unlock(&http_lock);
-
-  tvh_write(http_pipe.wr, "n", 1);
-
-  return hc;
-}
-
-/*
- * Cancel
- */
-void
-http_close ( http_client_t *hc )
-{
-  pthread_mutex_lock(&http_lock);
-  http_remove(hc);
-  free(hc);
-  pthread_mutex_unlock(&http_lock);
-}
-
-/*
- * Initialise subsystem
- */
-pthread_t http_client_tid;
-
-void
-http_client_init ( void )
-{
-  tvhpoll_event_t ev = { 0 };
-
-  /* Setup list */
-  pthread_mutex_init(&http_lock, NULL);
-  TAILQ_INIT(&http_clients);
-
-  /* Initialise curl */
-  curl_global_init(CURL_GLOBAL_ALL);
-  http_curlm = curl_multi_init();
-  curl_multi_setopt(http_curlm, CURLMOPT_SOCKETFUNCTION, http_curl_socket);
-
-  /* Setup pipe */
-  tvh_pipe(O_NONBLOCK, &http_pipe);
-
-  /* Setup poll */
-  http_poll   = tvhpoll_create(10);
-  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_thread, NULL, 0);
-}
-
-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);
-  curl_multi_cleanup(http_curlm);
-}
-
-
-void
-curl_done ( void )
-{
-#if ENABLE_NSPR
-  void PR_Cleanup( void );
-#endif
-  curl_global_cleanup();
-#if ENABLE_NSPR
-  /*
-   * Note: Curl depends on the NSPR library.
-   *       The PR_Cleanup() call is mandatory to free NSPR resources.
-   */
-  PR_Cleanup();
-#endif
-}
-
-#else /* ENABLE_CURL */
-
-void 
-http_client_init ( void )
-{
-}
-
-void
-http_client_done ( void )
-{
-}
-
-void
-curl_done ( void )
-{
-}
-
-#endif /* ENABLE_CURL */
diff --git a/src/httpc.c b/src/httpc.c
new file mode 100644 (file)
index 0000000..5e3a523
--- /dev/null
@@ -0,0 +1,1645 @@
+/*
+ *  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>
+
+#define HTTPCLIENT_TESTSUITE 1
+
+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 th_pipe_t                http_pipe;
+
+/*
+ *
+ */
+static int
+http_port( const char *scheme, int port )
+{
+  if (port <= 0 || port > 65535) {
+    if (strcmp(scheme, "http") == 0)
+      port = 80;
+    else if (strcmp(scheme, "https") == 0)
+      port = 443;
+    else if (strcmp(scheme, "rtsp") == 0)
+      port = 554;
+    else {
+      tvhlog(LOG_ERR, "httpc", "Unknown scheme '%s'", scheme);
+      return -EINVAL;
+    }
+  }
+  return port;
+}
+
+/*
+ * Disable
+ */
+static void
+http_client_shutdown ( http_client_t *hc, int force )
+{
+  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;
+    if (hc->hc_efd == http_poll)
+      TAILQ_REMOVE(&http_clients, hc, hc_link);
+    memset(&ev, 0, sizeof(ev));
+    ev.fd       = hc->hc_fd;
+    tvhpoll_rem(efd = hc->hc_efd, &ev, 1);
+    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 )
+{
+  if (result < 0)
+    http_client_shutdown(hc, 0);
+  hc->hc_result       = result;
+  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 -EBADFD;
+  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 = ESTRPIPE;
+    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 = ESTRPIPE;
+      return -1;
+    } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) {
+      errno = EBADFD;
+      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;
+
+  while (1) {
+    if (!ssl->connected) {
+      r = SSL_connect(ssl->ssl);
+      if (r > 0) {
+        ssl->connected = 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 = EBADFD;
+      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);
+      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 = EBADFD;
+      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_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);
+    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_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);
+    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 )
+{
+  ssize_t l, l2, csize;
+  int res, end = 0;
+
+  buf[len] = '\0';
+
+  if (len == 0) {
+    if (hc->hc_csize == -1 || hc->hc_rpos >= hc->hc_csize)
+      return 1;
+    return 0;
+  }  
+
+  csize = hc->hc_csize < 0 ? 0 : hc->hc_csize;
+  l = len;
+  if (hc->hc_csize && hc->hc_csize != -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;
+  http_ver_t 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 != ESTRPIPE) {
+          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, -ESTRPIPE);
+  }
+  if (r < 0) {
+    if (errno == ESTRPIPE && 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);
+    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)
+    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) {
+    if (hc->hc_keepalive && strcasecmp(p, "keep-alive"))
+      return http_client_flush(hc, -EINVAL);
+    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;
+  hc->hc_in_data = 1;
+  res = http_client_data_received(hc, hc->hc_rbuf + hc->hc_hsize, len);
+  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);
+  http_arg_set(h, "Host", url->host);
+  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]) {
+    size_t plen = strlen(url->pass);
+    size_t ulen = strlen(url->user);
+    size_t len = BASE64_SIZE(plen) + 1;
+    char *buf = alloca(ulen + 1 + len + 1);
+    strcpy(buf, url->user);
+    base64_encode(buf + ulen + 1, len, (uint8_t *)url->pass, plen);
+    buf[ulen] = ':';
+    http_arg_set(h, "Authorization", buf);
+  }
+}
+
+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);
+    r = http_client_reconnect(hc, hc->hc_version,
+                              u.scheme, u.host, u.port);
+    if (r < 0) {
+      urlreset(&u);
+      return 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);
+}
+
+/*
+ * 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;
+      http_client_run(hc);
+      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;
+  hc->hc_fd      = tcp_connect(host, port, 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;
+  }
+  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 )
+{
+  http_client_t *hc;
+
+  hc             = calloc(1, sizeof(http_client_t));
+  hc->hc_aux     = aux;
+  hc->hc_io_size = 1024;
+
+  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
+ */
+void
+http_client_close ( http_client_t *hc )
+{
+  http_client_wcmd_t *wcmd;
+
+  if (hc == NULL)
+    return;
+
+  pthread_mutex_lock(&http_lock);
+  http_client_shutdown(hc, 1);
+  http_client_flush(hc, 0);
+  pthread_mutex_unlock(&http_lock);
+  while ((wcmd = TAILQ_FIRST(&hc->hc_wqueue)) != NULL)
+    http_client_cmd_destroy(hc, wcmd);
+  http_client_ssl_free(hc);
+  free(hc->hc_location);
+  free(hc->hc_rbuf);
+  free(hc->hc_data);
+  free(hc->hc_host);
+  free(hc->hc_scheme);
+  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);
+  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=%li)\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=%li)\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 },
+  { "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 },
+  { "ERFKILL",         ERFKILL },
+  { "EHWPOISON",       EHWPOISON },
+};
+
+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;
+  int handle_location = 0;
+
+  path = getenv("TVHEADEND_HTTPC_TEST");
+  if (path == NULL)
+    path = TVHEADEND_DATADIR "/support/httpc-test.txt";
+  fp = fopen(path, "r");
+  if (fp == NULL) {
+    tvhlog(LOG_ERR, "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;
+      handle_location = 0;
+    } 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 (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, "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)
+        goto fatal;
+    } else if (strncmp(s, "Command=", 8) == 0) {
+      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;
+      if (http_arg_get(&args, "Host") == NULL && u1.host && u1.host[0] != '\0')
+        http_arg_set(&args, "Host", u1.host);
+      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);
+        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);
+        }
+      }
+      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, val2str(r, ERRNO_tab));
+        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)
+          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");
+  abort();
+}
+
+#endif
index 746397cc9e1c926aa7b0e40749140a4ef24eb148..770dff4a84ae2eca0c755c6bee6caa297c393e3f 100644 (file)
 #include "iptv_private.h"
 #include "http.h"
 
-#if ENABLE_CURL
-
 /*
  * Connected
  */
-static void
-iptv_http_conn ( void *p )
+static int
+iptv_http_header ( http_client_t *hc )
 {
   pthread_mutex_lock(&global_lock);
-  iptv_input_mux_started(p);
+  iptv_input_mux_started(hc->hc_aux);
   pthread_mutex_unlock(&global_lock);
+  return 0;
 }
 
 /*
  * Receive data
  */
-static size_t
+static int
 iptv_http_data
-  ( void *p, void *buf, size_t len )
+  ( http_client_t *hc, void *buf, size_t len )
 {
-  iptv_mux_t *im = p;
+  iptv_mux_t *im = hc->hc_aux;
 
   pthread_mutex_lock(&iptv_lock);
 
@@ -52,7 +51,7 @@ iptv_http_data
 
   pthread_mutex_unlock(&iptv_lock);
 
-  return len;
+  return 0;
 }
 
 /*
@@ -63,8 +62,19 @@ iptv_http_start
   ( iptv_mux_t *im, const url_t *u )
 {
   http_client_t *hc;
-  if (!(hc = http_connect(u, iptv_http_conn, iptv_http_data, NULL, im)))
+  int r;
+
+  if (!(hc = http_client_connect(im, HTTP_VERSION_1_1, u->scheme,
+                                 u->host, u->port)))
+    return SM_CODE_TUNING_FAILED;
+  hc->hc_hdr_received  = iptv_http_header;
+  hc->hc_data_received = iptv_http_data;
+  http_client_register(hc);
+  r = http_client_simple(hc, u);
+  if (r < 0) {
+    http_client_close(hc);
     return SM_CODE_TUNING_FAILED;
+  }
   im->im_data = hc;
 
   return 0;
@@ -77,7 +87,7 @@ static void
 iptv_http_stop
   ( iptv_mux_t *im )
 {
-  http_close(im->im_data);
+  http_client_close(im->im_data);
 }
 
 
@@ -102,12 +112,3 @@ iptv_http_init ( void )
   };
   iptv_handler_register(ih, 2);
 }
-
-#else /* ENABLE_CURL */
-
-void
-iptv_http_init ( void )
-{
-}
-
-#endif /* ENABLE_CURL */
index 8dd439c3bc326cf5efca64de86b3a2f6617ca0a9..c4798ab49cf82a2e28e110d54eae7dafaabf29e3 100644 (file)
@@ -444,7 +444,6 @@ typedef struct satip_discovery {
   url_t url;
   http_client_t *http_client;
   time_t http_start;
-  char *desc;
 } satip_discovery_t;
 
 TAILQ_HEAD(satip_discovery_queue, satip_discovery);
@@ -465,7 +464,7 @@ satip_discovery_destroy(satip_discovery_t *d, int unlink)
     TAILQ_REMOVE(&satip_discoveries, d, disc_link);
   }
   if (d->http_client)
-    http_close(d->http_client);
+    http_client_close(d->http_client);
   urlreset(&d->url);
   free(d->myaddr);
   free(d->location);
@@ -474,7 +473,6 @@ satip_discovery_destroy(satip_discovery_t *d, int unlink)
   free(d->bootid);
   free(d->configid);
   free(d->deviceid);
-  free(d->desc);
   free(d);
 }
 
@@ -489,11 +487,10 @@ satip_discovery_find(satip_discovery_t *d)
   return NULL;
 }
 
-static size_t
-satip_discovery_http_data(void *p, void *buf, size_t len)
+static void
+satip_discovery_http_closed(http_client_t *hc, int errn)
 {
-  satip_discovery_t *d = p;
-  size_t size;
+  satip_discovery_t *d = hc->hc_aux;
   char *s;
   htsmsg_t *xml = NULL, *tags, *root, *device;
   const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc;
@@ -503,24 +500,27 @@ satip_discovery_http_data(void *p, void *buf, size_t len)
   satip_device_info_t info;
   char errbuf[100];
 
-  size = d->desc ? strlen(d->desc) : 0;
-  if (len + size > 16384)
-    goto finish;
-  d->desc = realloc(d->desc, size + len + 1);
-  memcpy(d->desc + size, buf, len);
-  size += len;
-  d->desc[size] = '\0';
+  s = http_arg_get(&hc->hc_args, "Content-Type");
+  if (s && strcasecmp(s, "text/xml")) {
+    errn = EMEDIUMTYPE;
+    s = NULL;
+  }
+  if (errn != 0 || s == NULL || hc->hc_code != 200 ||
+      hc->hc_data_size == 0 || hc->hc_data == NULL) {
+    tvhlog(LOG_ERR, "satip", "Cannot get %s: %s", d->location, strerror(errn));
+    return;
+  }
 
-  s = d->desc + size - 1;
-  while (s != d->desc && *s != '/')
+  s = hc->hc_data + hc->hc_data_size - 1;
+  while (s != hc->hc_data && *s != '/')
     s--;
-  if (s != d->desc)
+  if (s != hc->hc_data)
     s--;
   if (strncmp(s, "</root>", 7))
-    return len;
+    return;
   /* Parse */
-  xml = htsmsg_xml_deserialize(d->desc, errbuf, sizeof(errbuf));
-  d->desc = NULL;
+  xml = htsmsg_xml_deserialize(hc->hc_data, errbuf, sizeof(errbuf));
+  hc->hc_data = NULL;
   if (!xml) {
     tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf);
     goto finish;
@@ -599,21 +599,13 @@ satip_discovery_http_data(void *p, void *buf, size_t len)
   free(info.tunercfg);
 finish:
   htsmsg_destroy(xml);
-  return -EIO;
-}
-
-static void
-satip_discovery_http_fail(void *p)
-{
-  pthread_mutex_lock(&global_lock);
-  satip_discovery_destroy((satip_discovery_t *)p, 1);
-  pthread_mutex_unlock(&global_lock);
 }
 
 static void
 satip_discovery_timerq_cb(void *aux)
 {
   satip_discovery_t *d, *next;
+  int r;
 
   lock_assert(&global_lock);
 
@@ -626,14 +618,19 @@ satip_discovery_timerq_cb(void *aux)
         satip_discovery_destroy(d, 1);
       continue;
     }
-    d->http_client = http_connect(&d->url, NULL,
-                                  satip_discovery_http_data,
-                                  satip_discovery_http_fail,
-                                  d);
+
+    d->http_client = http_client_connect(d, HTTP_VERSION_1_1, d->url.scheme,
+                                         d->url.host, d->url.port);
     if (d->http_client == NULL)
       satip_discovery_destroy(d, 1);
-    else
+    else {
       d->http_start = dispatch_clock;
+      d->http_client->hc_conn_closed = satip_discovery_http_closed;
+      http_client_register(d->http_client);
+      r = http_client_simple(d->http_client, &d->url);
+      if (r < 0)
+        satip_discovery_destroy(d, 1);
+    }
   }
   if (TAILQ_FIRST(&satip_discoveries))
     gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5);
index a9f2117a37a0a215771811240f7c8021df20fe2d..be473a848cbb5d05b770cb9775cec074890702d2 100644 (file)
@@ -73,7 +73,8 @@ satip_rtsp_send_partial( satip_rtsp_connection_t *conn )
   while (1) {
     r = send(conn->fd, conn->wbuf + conn->wpos, conn->wsize - conn->wpos, MSG_DONTWAIT);
     if (r < 0) {
-      if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+      if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ||
+          errno == EINPROGRESS)
         return SATIP_RTSP_INCOMPLETE;
       return -errno;
     }
@@ -238,7 +239,7 @@ satip_rtsp_options_decode( satip_rtsp_connection_t *conn )
 {
   char *argv[32], *s, *saveptr;
   int i, n, what = 0;
-  
+
   s = strtok_r(conn->header, "\n", &saveptr);
   while (s) {
     n = http_tokenize(s, argv, 32, ',');
index e8e2ff8d9ccc15b89e98b9593e908ade76f37bee..fba0b10ca9c9729a741ef335382e92565cfe83c6 100644 (file)
 #ifdef PLATFORM_LINUX
 #include <sys/prctl.h>
 #endif
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/engine.h>
 
 pthread_t main_tid;
 
@@ -730,6 +735,11 @@ main(int argc, char **argv)
   sigfillset(&set);
   sigprocmask(SIG_BLOCK, &set, NULL);
   trap_init(argv[0]);
+
+  /* SSL library init */
+  OPENSSL_config(NULL);
+  SSL_load_error_strings();
+  SSL_library_init();
   
   /* Initialise configuration */
   uuid_init();
@@ -872,7 +882,21 @@ main(int argc, char **argv)
     
   free(opt_tsfile.str);
 
-  curl_done();
+  /* OpenSSL - welcome to the "cleanup" hell */
+  ENGINE_cleanup();
+  RAND_cleanup();
+  CRYPTO_cleanup_all_ex_data();
+  EVP_cleanup();
+  CONF_modules_free();
+  COMP_zlib_cleanup();
+  ERR_remove_state(0);
+  ERR_free_strings();
+  {
+    struct stack_st_SSL_COMP * pCOMP = SSL_COMP_get_compression_methods();
+    if (pCOMP)
+     sk_SSL_COMP_free(pCOMP);
+  }
+  /* end of OpenSSL cleanup code */
 
   return 0;
 }
index 272ba787a77e2f2bab849ec48efe6076cd18ba16..af60bc10fa88864612e2847f278426769ea8120d 100644 (file)
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -142,7 +142,9 @@ tcp_connect(const char *hostname, int port, char *errbuf, size_t errbufsize,
   free(tmphstbuf);
 
   if(r == -1) {
-    if(errno == EINPROGRESS) {
+    if(errno == EINPROGRESS && timeout < 0) {
+      err = 0;
+    } else if(errno == EINPROGRESS) {
       struct pollfd pfd;
 
       pfd.fd = fd;
index 15031f4552ec251817a70bd2d06b31649252bcca..f0601ab163ed8d25d1bb7a4a3103773d5c988df8 100644 (file)
@@ -575,6 +575,11 @@ uint32_t tvh_crc32(const uint8_t *data, size_t datalen, uint32_t crc);
 
 int base64_decode(uint8_t *out, const char *in, int out_size);
 
+char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size);
+
+/* Calculate the output size needed to base64-encode x bytes. */
+#define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1)
+
 int put_utf8(char *out, int c);
 
 static inline int64_t ts_rescale(int64_t ts, int tb)
index 7861b57a838499f890abdc3877f0bbb8b32f8d63..6278facc218a0fde07fc899f8ea4c1206206c6ae 100644 (file)
@@ -193,6 +193,41 @@ base64_decode(uint8_t *out, const char *in, int out_size)
     return dst - out;
 }
 
+/*
+ * b64_encode: Stolen from VLC's http.c.
+ * Simplified by Michael.
+ * Fixed edge cases and made it work from data (vs. strings) by Ryan.
+ */
+
+char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size)
+{
+    static const char b64[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    char *ret, *dst;
+    unsigned i_bits = 0;
+    int i_shift = 0;
+    int bytes_remaining = in_size;
+
+    if (in_size >= UINT_MAX / 4 ||
+        out_size < BASE64_SIZE(in_size))
+        return NULL;
+    ret = dst = out;
+    while (bytes_remaining) {
+        i_bits = (i_bits << 8) + *in++;
+        bytes_remaining--;
+        i_shift += 8;
+
+        do {
+            *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
+            i_shift -= 6;
+        } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0));
+    }
+    while ((dst - ret) & 3)
+        *dst++ = '=';
+    *dst = '\0';
+
+    return ret;
+}
 
 /**
  *
diff --git a/support/httpc-test.txt b/support/httpc-test.txt
new file mode 100644 (file)
index 0000000..26bb6e9
--- /dev/null
@@ -0,0 +1,81 @@
+#
+# File format:
+#  Header=           pass this in the http request
+#  URL=              URL
+#  HandleLocation=   Handle the location (redirection) requests (0 or 1)
+#  DataTransfer=all  grab all data and pass then in the data_complete callback
+#  DataTransfer=cont continuous passing data to the data_received callback
+#  DataLimit=        limit data to these bytes
+#  ExpectedError=    expected error
+#  Reset=1           reset the initial state - close the current keep-alive conn
+#
+
+Header=Connection: close
+URL=http://www.google.com
+DataTransfer=all
+HandleLocation=1
+Command=GET
+
+Header=Connection: close
+URL=http://www.google.com
+DataTransfer=all
+DataLimit=10
+ExpectedError=EOVERFLOW
+HandleLocation=1
+Command=GET
+
+#
+# Keep-alive connection test
+#
+
+Reset=1
+
+DataTransfer=all
+URL=http://httpbin.org
+Command=GET
+URL=http://httpbin.org
+Command=GET
+URL=http://httpbin.org
+Command=GET
+HandleLocation=1
+URL=http://httpbin.org/relative-redirect/20
+ExpectedError=ELOOP
+Command=GET
+
+#
+# Keep-alive SSL test
+#
+
+Reset=1
+
+URL=https://httpbin.org
+Command=GET
+URL=https://httpbin.org
+Command=GET
+URL=https://httpbin.org
+Command=GET
+HandleLocation=1
+URL=https://httpbin.org/relative-redirect/20
+ExpectedError=ELOOP
+Command=GET
+
+#
+# Google SSL test
+#
+
+Reset=1
+
+Header=Connection: close
+URL=https://www.google.com
+DataTransfer=all
+HandleLocation=1
+Command=GET
+
+Header=Connection: close
+URL=https://www.google.com
+DataTransfer=all
+DataLimit=10
+HandleLocation=1
+ExpectedError=EOVERFLOW
+Command=GET
+