]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Rewrite imagecache to use build-in http client (remove curl)
authorJaroslav Kysela <perex@perex.cz>
Tue, 15 Apr 2014 17:35:12 +0000 (19:35 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 5 May 2014 20:00:35 +0000 (22:00 +0200)
- add the basic SSL peer certificate verification

configure
src/http.h
src/httpc.c
src/imagecache.c
src/tvhpoll.c
support/httpc-test.txt

index 839e53c9065474bbe50f37d51eff737b4567d46f..161c75935c607de44572c9ecaa99691d7f5296fc 100755 (executable)
--- a/configure
+++ b/configure
@@ -30,7 +30,6 @@ OPTIONS=(
   "zlib:auto"
   "libav:auto"
   "inotify:auto"
-  "curl:auto"
   "epoll:auto"
   "uriparser:auto"
   "ccache:auto"
@@ -161,29 +160,11 @@ if enabled_or_auto zlib; then
   fi
 fi
 
-#
-# CURL
-#
-if enabled_or_auto curl; then
-  if check_pkg libcurl; then
-    if check_pkg nspr; then
-      enable nspr
-    fi
-    enable curl
-  elif enabled curl; then
-    die "Curl development support not foun (use --disable-curl)"
-  fi
-fi
-
 #
 # SAT>IP client
 #
 if enabled_or_auto satip_client; then
-  if enabled curl; then
-    enable upnp
-  else
-    die "SAT>IP client requires curl enabled"
-  fi
+  enable upnp
 fi
 
 #
@@ -279,11 +260,7 @@ fi
 # Icon caching
 #
 if enabled_or_auto imagecache; then
-  if enabled curl; then
-    enable imagecache
-  elif enabled imagecache; then
-    die "Libcurl support not found (use --disable-imagecache)"
-  fi  
+  enable imagecache
 fi
 
 #
index 08ab0c3d8dce9553de464447ee9a0e60bfafcbd7..5ba944bf1e225c14aee4e27ad60177eba4af3cd8 100644 (file)
@@ -264,6 +264,8 @@ struct http_client {
   http_client_wcmd_t            *hc_wcmd;
   TAILQ_HEAD(,http_client_wcmd)  hc_wqueue;
 
+  int          hc_verify_peer;  /* SSL - verify peer */
+
   int          hc_cseq;         /* RTSP */
   int          hc_rcseq;        /* RTSP - expected cseq */
   char        *hc_rtsp_session;
@@ -298,6 +300,7 @@ int http_client_send( http_client_t *hc, http_cmd_t cmd,
 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 );
+void http_client_ssl_peer_verify( http_client_t *hc, int verify );
 
 /*
  * RTSP helpers
index c9c845b65ad5c75b70b161561b2f652d716a267e..a1ffe3eeced6ddcb495bac75c286161ac5dd4a3a 100644 (file)
@@ -30,7 +30,9 @@
 #include <openssl/err.h>
 #include <openssl/ssl.h>
 
+#if ENABLE_TRACE 
 #define HTTPCLIENT_TESTSUITE 1
+#endif
 
 struct http_client_ssl {
   int      connected;
@@ -345,11 +347,24 @@ http_client_ssl_send( http_client_t *hc, const void *buf, size_t len )
   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 {
@@ -1012,6 +1027,9 @@ http_client_redirected ( http_client_t *hc )
       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;
   }
 
@@ -1043,6 +1061,26 @@ http_client_simple( http_client_t *hc, const url_t *url )
                           &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
  */
@@ -1120,6 +1158,7 @@ http_client_reconnect
     tvhlog(LOG_ERR, "httpc", "Unable to connect to %s:%i - %s", host, port, errbuf);
     return -EINVAL;
   }
+  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));
@@ -1175,6 +1214,7 @@ http_client_connect
   hc->hc_aux     = aux;
   hc->hc_io_size = 1024;
   hc->hc_rtsp_stream_id = -1;
+  hc->hc_verify_peer = -1;
 
   TAILQ_INIT(&hc->hc_args);
   TAILQ_INIT(&hc->hc_wqueue);
@@ -1482,13 +1522,14 @@ http_client_testsuite_run( void )
   FILE *fp;
   int r, expected = HTTP_CON_DONE;
   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_ERR, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
+    tvhlog(LOG_NOTICE, "httpc", "Test: unable to open '%s': %s", path, strerror(errno));
     return;
   }
   memset(&u1, 0, sizeof(u1));
@@ -1521,6 +1562,7 @@ http_client_testsuite_run( void )
       port = 0;
       expected = HTTP_CON_DONE;
       handle_location = 0;
+      peer_verify = 1;
     } else if (strcmp(s, "DataTransfer=all") == 0) {
       data_transfer = 0;
     } else if (strcmp(s, "DataTransfer=cont") == 0) {
@@ -1529,6 +1571,10 @@ http_client_testsuite_run( void )
       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) {
@@ -1558,6 +1604,8 @@ http_client_testsuite_run( void )
       if (urlparse(s + 4, &u1) < 0)
         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;
@@ -1579,6 +1627,7 @@ http_client_testsuite_run( void )
         } 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);
index bc219acd16403e8b0514381a59a55a8530ad51f2..cf17f01d29a83ed6a26ef895bbafb52366e9406d 100644 (file)
 #include "redblack.h"
 #include "notify.h"
 #include "prop.h"
-
-#if ENABLE_IMAGECACHE
-#define CURL_STATICLIB
-#include <curl/curl.h>
-#include <curl/easy.h>
-#endif
+#include "http.h"
 
 /*
  * Image metadata
@@ -139,10 +134,15 @@ imagecache_image_add ( imagecache_image_t *img )
 static int
 imagecache_image_fetch ( imagecache_image_t *img )
 {
-  int res = 1;
-  CURL *curl;
+  int res = 1, r;
   FILE *fp;
+  url_t url;
   char tmp[256], path[256];
+  tvhpoll_event_t ev;
+  tvhpoll_t *efd = NULL;
+  http_client_t *hc;
+
+  memset(&url, 0, sizeof(url));
 
   /* Open file  */
   if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d",
@@ -156,25 +156,54 @@ imagecache_image_fetch ( imagecache_image_t *img )
   
   /* Build command */
   tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url);
-  curl = curl_easy_init();
-  curl_easy_setopt(curl, CURLOPT_URL,         img->url);
-  curl_easy_setopt(curl, CURLOPT_WRITEDATA,   fp);
-  curl_easy_setopt(curl, CURLOPT_USERAGENT,   "TVHeadend");
-  curl_easy_setopt(curl, CURLOPT_TIMEOUT,     120);
-  curl_easy_setopt(curl, CURLOPT_NOPROGRESS,  1);
-  curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-  if (imagecache_conf.ignore_sslcert)
-    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+  memset(&url, 0, sizeof(url));
+  if (urlparse(img->url, &url)) {
+    tvherror("imagecache", "Unable to parse url '%s'", img->url);
+    goto error;
+  }
 
   /* Fetch (release lock, incase of delays) */
   pthread_mutex_unlock(&global_lock);
-  res = curl_easy_perform(curl);
-  curl_easy_cleanup(curl);
-  fclose(fp);
+
+  hc = http_client_connect(NULL, HTTP_VERSION_1_1, url.scheme,
+                           url.host, url.port);
+  if (hc == NULL)
+    goto error;
+
+  http_client_ssl_peer_verify(hc, imagecache_conf.ignore_sslcert ? 0 : 1);
+  hc->hc_handle_location = 1;
+  hc->hc_data_limit  = 256*1024;
+  efd = tvhpoll_create(1);
+  hc->hc_efd = efd;
+
+  r = http_client_simple(hc, &url);
+  if (r < 0)
+    goto error;
+
+  while (tvheadend_running) {
+    r = tvhpoll_wait(efd, &ev, 1, -1);
+    if (r < 0)
+      break;
+    if (r == 0)
+      continue;
+    r = http_client_run(hc);
+    if (r < 0)
+      break;
+    if (r == HTTP_CON_DONE) {
+      if (hc->hc_code == HTTP_STATUS_OK && hc->hc_data_size > 0) {
+        fwrite(hc->hc_data, hc->hc_data_size, 1, fp);
+        res = 0;
+      }
+      break;
+    }
+  }
+
   pthread_mutex_lock(&global_lock);
 
   /* Process */
 error:
+  urlreset(&url);
+  tvhpoll_destroy(efd);
   img->state = IDLE;
   time(&img->updated); // even if failed (possibly request sooner?)
   if (res) {
index 29e5893601f55f6258a8b431a692bb5d011c5daf..1783dc12c4c24d041f39e9eda2a76b77a69cdb96 100644 (file)
@@ -91,6 +91,8 @@ tvhpoll_create ( size_t n )
 
 void tvhpoll_destroy ( tvhpoll_t *tp )
 {
+  if (tp == NULL)
+    return;
 #if ENABLE_EPOLL || ENABLE_KQUEUE
   free(tp->ev);
   close(tp->fd);
index 26bb6e9b94f3c35f20a0add68bcea1e0929a31a4..418bc25489e05ff8f1b48c7389466bb280caa54c 100644 (file)
@@ -7,7 +7,9 @@
 #  DataTransfer=cont continuous passing data to the data_received callback
 #  DataLimit=        limit data to these bytes
 #  ExpectedError=    expected error
+#  SSLPeerVerify=    Enable SSL/TLS peer verification (0 or 1)
 #  Reset=1           reset the initial state - close the current keep-alive conn
+#  Command=          HTTP/RTSP command or EXIT
 #
 
 Header=Connection: close
@@ -78,4 +80,3 @@ DataLimit=10
 HandleLocation=1
 ExpectedError=EOVERFLOW
 Command=GET
-