support chunked HTTP responses
authorAndreas Steffen <andreas.steffen@strongswan.org>
Wed, 3 Apr 2013 10:08:52 +0000 (12:08 +0200)
committerAndreas Steffen <andreas.steffen@strongswan.org>
Wed, 3 Apr 2013 19:38:04 +0000 (21:38 +0200)
src/libcharon/plugins/tnc_ifmap/Makefile.am
src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.c [new file with mode: 0644]
src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.h [new file with mode: 0644]
src/libcharon/plugins/tnc_ifmap/tnc_ifmap_soap_msg.c

index 7d8b0f5..36d9316 100644 (file)
@@ -22,6 +22,7 @@ libstrongswan_tnc_ifmap_la_SOURCES = \
        tnc_ifmap_listener.h tnc_ifmap_listener.c \
        tnc_ifmap_soap.h tnc_ifmap_soap.c \
        tnc_ifmap_soap_msg.h tnc_ifmap_soap_msg.c \
+       tnc_ifmap_http.h tnc_ifmap_http.c \
        tnc_ifmap_renew_session_job.h tnc_ifmap_renew_session_job.c
 
 libstrongswan_tnc_ifmap_la_LDFLAGS = -module -avoid-version
diff --git a/src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.c b/src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.c
new file mode 100644 (file)
index 0000000..e2a1582
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2013 Andreas Steffen
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * 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 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE /* for asprintf() */
+
+#include "tnc_ifmap_http.h"
+
+#include <utils/debug.h>
+#include <utils/lexparser.h>
+
+#include <stdio.h>
+
+typedef struct private_tnc_ifmap_http_t private_tnc_ifmap_http_t;
+
+/**
+ * Private data of an tnc_ifmap_http_t object.
+ */
+struct private_tnc_ifmap_http_t {
+
+       /**
+        * Public tnc_ifmap_http_t interface.
+        */
+       tnc_ifmap_http_t public;
+
+       /**
+        * HTTPS Server URI with https:// prefix removed
+        */
+       char *uri;
+
+       /**
+        * Optional base64-encoded username:password for HTTP Basic Authentication
+        */
+       chunk_t user_pass;
+
+       /**
+        * HTTP chunked mode
+        */
+       bool chunked;
+
+};
+
+METHOD(tnc_ifmap_http_t, build, status_t,
+       private_tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out)
+{
+       char *host, *path, *request, auth[128];
+       int len;
+
+       /* Duplicate host[/path] string since we are going to manipulate it */
+       len = strlen(this->uri) + 2;
+       host = malloc(len);
+       memset(host, '\0', len);
+       strcpy(host, this->uri);
+
+       /* Extract appended path or set to root */
+       path = strchr(host, '/');
+       if (!path)
+       {
+               path = host + len - 2;
+               *path = '/';
+       }
+
+       /* Use Basic Authentication? */
+       if (this->user_pass.len)
+       {
+               snprintf(auth, sizeof(auth), "Authorization: Basic %.*s\r\n",
+                                this->user_pass.len, this->user_pass.ptr);
+       }
+       else
+       {
+               *auth = '\0';
+       }
+
+       /* Write HTTP POST request, TODO break up into chunks */
+       len = asprintf(&request,
+                       "POST %s HTTP/1.1\r\n"
+                       "Host: %.*s\r\n"
+                       "%s"
+                       "Content-Type: application/soap+xml;charset=utf-8\r\n"
+                       "Content-Length: %d\r\n"
+                       "\r\n"
+                       "%.*s", path, (path-host), host, auth, in->len, in->len, in->ptr);
+       free(host);
+
+       if (len == -1)
+       {
+               return FAILED;
+       }
+       *out = chunk_create(request, len);
+       DBG3(DBG_TLS, "sending HTML POST request %B", out);
+
+       return SUCCESS;
+}
+
+static bool process_header(chunk_t *in, bool *chunked, u_int *content_len)
+{
+       chunk_t line, version, parameter;
+       int code;
+       u_int len;
+
+       /* Process HTTP protocol version */
+       if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) ||
+               !match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", &code) != 1)
+       {
+               DBG1(DBG_TNC, "malformed http response header");
+               return FALSE;
+       }
+       if (code != 200)
+       {
+               DBG1(DBG_TNC, "http response returns error code %d", code);
+               return FALSE;
+       }       
+
+       *content_len = 0;
+       *chunked = FALSE;
+
+       /* Process HTTP header line by line until the HTTP body is reached */
+       while (fetchline(in, &line))
+       {
+               if (line.len == 0)
+               {
+                       break;
+               }
+               if (extract_token(&parameter, ':', &line) && eat_whitespace(&line))
+               {
+                       if (match("Content-Length", &parameter))
+                       {
+                               if (sscanf(line.ptr, "%u", &len) == 1)
+                               {
+                                       *content_len = len;
+                               }
+                       }
+                       else if (match("Transfer-Encoding", &parameter) &&
+                                        match("chunked", &line))
+                       {
+                               *chunked = TRUE;
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
+METHOD(tnc_ifmap_http_t, process, status_t,
+       private_tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out)
+{
+       u_int len = 0;
+       chunk_t line, out_chunk;
+
+       DBG3(DBG_TLS, "receiving HTTP response %B", in);
+
+       if (!this->chunked)
+       {
+               if (!process_header(in, &this->chunked, &len))
+               {
+                       return FAILED;
+               }
+       }
+
+       while (in->len)
+       {
+               if (this->chunked)
+               {
+                       if (!fetchline(in, &line) || sscanf(line.ptr, "%x", &len) != 1)
+                       {
+                               return FAILED;
+                       }
+                       DBG3(DBG_TLS, "received HTTP response is chunked (%u bytes)", len);
+
+                       /* Received last chunk? */
+                       if (len == 0)
+                       {
+                               return SUCCESS;
+                       }
+               }
+
+               /* Check size of of remaining HTTP body */
+               if (len > in->len)
+               {
+                       DBG1(DBG_TNC, "insufficient data in HTTP body");
+                       return FAILED;
+               }
+
+               if (this->chunked)
+               {
+                       out_chunk = *in;
+                       out_chunk.len = len;
+                       *out = chunk_cat("mc", *out, out_chunk); 
+                       *in = chunk_skip(*in, len);
+                       if (!fetchline(in, &line) || line.len > 0)
+                       {
+                               return FAILED;
+                       }               
+               }
+               else
+               {
+                       if (len)
+                       {
+                               in->len = len;
+                       }
+                       *out = chunk_clone(*in);
+                       return SUCCESS;
+               }
+       }
+       return NEED_MORE;
+}
+
+METHOD(tnc_ifmap_http_t, destroy, void,
+       private_tnc_ifmap_http_t *this)
+{
+       free(this);
+}
+
+/**
+ * See header
+ */
+tnc_ifmap_http_t *tnc_ifmap_http_create(char *uri, chunk_t user_pass)
+{
+       private_tnc_ifmap_http_t *this;
+
+       INIT(this,
+               .public = {
+                       .build = _build,
+                       .process = _process,
+                       .destroy = _destroy,
+               },
+               .uri = uri,
+               .user_pass = user_pass,
+       );
+
+       return &this->public;
+}
+
diff --git a/src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.h b/src/libcharon/plugins/tnc_ifmap/tnc_ifmap_http.h
new file mode 100644 (file)
index 0000000..3d30847
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 Andreas Steffen
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * 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 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+/**
+ * @defgroup tnc_ifmap_http tnc_ifmap_http
+ * @{ @ingroup tnc_ifmap 
+ */
+
+#ifndef TNC_IFMAP_HTTP_H_
+#define TNC_IFMAP_HTTP_H_
+
+#include <library.h>
+#include <tls_socket.h>
+
+#include <libxml/parser.h>
+
+typedef struct tnc_ifmap_http_t tnc_ifmap_http_t;
+
+/**
+ * Interface for building and processing HTTP messages
+ */
+struct tnc_ifmap_http_t {
+
+       /**
+        * Build a HTTP POST message
+        *
+        * @param in                    input data
+        * @param out                   HTTP POST request
+        * @result                              status return code
+        */
+       status_t (*build)(tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out);
+
+       /**
+        * Receive a HTTP [chunked] response
+        *
+        * @param in                    [chunked] HTTP response
+        * @param out                   output data
+        * @result                              status return code
+        */
+       status_t (*process)(tnc_ifmap_http_t *this, chunk_t *in, chunk_t *out);
+
+       /**
+        * Destroy a tnc_ifmap_http_t object.
+        */
+       void (*destroy)(tnc_ifmap_http_t *this);
+};
+
+/**
+ * Create a tnc_ifmap_http instance.
+ *
+ * @param uri                  HTTPS URI with https:// prefix removed
+ * @param user_pass            Optional username:password for HTTP Basic Authentication
+ */
+tnc_ifmap_http_t *tnc_ifmap_http_create(char *uri, chunk_t user_pass);
+
+#endif /** TNC_IFMAP_HTTP_H_ @}*/
index eaa8351..b862886 100644 (file)
  * for more details.
  */
 
-#define _GNU_SOURCE /* for asprintf() */
-
 #include "tnc_ifmap_soap_msg.h"
+#include "tnc_ifmap_http.h"
 
 #include <utils/debug.h>
-#include <utils/lexparser.h>
-
-#include <stdio.h>
 
 #define SOAP_NS                "http://www.w3.org/2003/05/soap-envelope"
 
@@ -37,17 +33,12 @@ struct private_tnc_ifmap_soap_msg_t {
        tnc_ifmap_soap_msg_t public;
 
        /**
-        * HTTPS Server URI with https:// prefix removed
-        */
-       char *uri;
-
-       /**
-        * Optional base64-encoded username:password for HTTP Basic Authentication
+        * HTTP POST request builder and response processing
         */
-       chunk_t user_pass;
+       tnc_ifmap_http_t *http;
 
        /**
-        * TLS Socket
+        * TLS socket
         */
        tls_socket_t *tls;
 
@@ -59,118 +50,6 @@ struct private_tnc_ifmap_soap_msg_t {
 };
 
 /**
- * Send HTTP POST request and receive HTTP response
- */
-static bool http_post(private_tnc_ifmap_soap_msg_t *this, chunk_t out,
-                                                                                                                  chunk_t *in)
-{
-       char *host, *path, *request, buf[2048];
-       chunk_t line, http, parameter;
-       int len, written, code, content_len = 0;
-
-       /* Duplicate host[/path] string since we are going to manipulate it */
-       len = strlen(this->uri) + 2;
-       host = malloc(len);
-       memset(host, '\0', len);
-       strcpy(host, this->uri);
-
-       /* Extract appended path or set to root */
-       path = strchr(host, '/');
-       if (!path)
-       {
-               path = host + len - 2;
-               *path = '/';
-       }
-
-       /* Use Basic Authentication? */
-       if (this->user_pass.len)
-       {
-               snprintf(buf, sizeof(buf), "Authorization: Basic %.*s\r\n",
-                                this->user_pass.len, this->user_pass.ptr);
-       }
-       else
-       {
-               *buf = '\0';
-       }
-
-       /* Write HTTP POST request */
-       len = asprintf(&request,
-                       "POST %s HTTP/1.1\r\n"
-                       "Host: %.*s\r\n"
-                       "%s"
-                       "Content-Type: application/soap+xml;charset=utf-8\r\n"
-                       "Content-Length: %d\r\n"
-                       "\r\n"
-                       "%.*s", path, (path-host), host, buf, out.len, out.len, out.ptr);
-       free(host);
-
-       if (len == -1)
-       {
-               return FALSE;
-       }
-       http = chunk_create(request, len);
-       DBG3(DBG_TLS, "%B", &http);
-
-       written = this->tls->write(this->tls, request, len);
-       free(request);
-       if (written != len)
-       {
-               return FALSE;
-       }
-
-       /* Read HTTP response */
-       len = this->tls->read(this->tls, buf, sizeof(buf), TRUE);
-       if (len <= 0)
-       {
-               return FALSE;
-       }
-       *in = chunk_create(buf, len);
-
-       /* Process HTTP protocol version */
-       if (!fetchline(in, &line) || !extract_token(&http, ' ', &line) ||
-               !match("HTTP/1.1", &http) || sscanf(line.ptr, "%d", &code) != 1)
-       {
-               DBG1(DBG_TNC, "malformed http response header");
-               return FALSE;
-       }
-       if (code != 200)
-       {
-               DBG1(DBG_TNC, "http response returns error code %d", code);
-               return FALSE;
-       }       
-
-       /* Process HTTP header line by line until the HTTP body is reached */
-       while (fetchline(in, &line))
-       {
-               if (line.len == 0)
-               {
-                       break;
-               }
-
-               if (extract_token(&parameter, ':', &line) &&
-                       match("Content-Length", &parameter) &&
-                       sscanf(line.ptr, "%d", &len) == 1)
-               {
-                       content_len = len;
-               }
-       }
-
-       /* Found Content-Length parameter and check size of HTTP body */
-       if (content_len)
-       {
-               if (content_len > in->len)
-               {
-                       DBG1(DBG_TNC, "http body is smaller than content length");
-                       return FALSE;
-               }
-               in->len = content_len;
-       }
-       *in = chunk_clone(*in);
-
-       return TRUE;
-}
-
-/**
  * Find a child node with a given name
  */
 static xmlNodePtr find_child(xmlNodePtr parent, const xmlChar* name)
@@ -198,9 +77,11 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
        xmlDocPtr doc;
        xmlNodePtr env, body, cur, response;
        xmlNsPtr ns;
-       xmlChar *xml, *errorCode, *errorString;
-       int len;
-       chunk_t in, out;
+       xmlChar *xml_str, *errorCode, *errorString;
+       int xml_len, len, written;
+       chunk_t xml, http;
+       char buf[4096];
+       status_t status;
 
        DBG2(DBG_TNC, "sending ifmap %s", request->name);
 
@@ -217,22 +98,58 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
        xmlAddChild(env, body);
 
        /* Convert XML Document into a character string */
-       xmlDocDumpFormatMemory(doc, &xml, &len, 1);
+       xmlDocDumpFormatMemory(doc, &xml_str, &xml_len, 1);
        xmlFreeDoc(doc);
-       DBG3(DBG_TNC, "%.*s", len, xml);
-       out = chunk_create(xml, len);
+       DBG3(DBG_TNC, "%.*s", xml_len, xml_str);
+       xml = chunk_create(xml_str, xml_len);
 
-       /* Send SOAP-XML request via HTTP POST */
-       if (!http_post(this, out, &in))
+       /* Send SOAP-XML request via HTTPS POST */
+       do
+       {
+               status = this->http->build(this->http, &xml, &http);
+               if (status == FAILED)
+               {
+                       break;
+               }
+               written = this->tls->write(this->tls, http.ptr, http.len);
+               free(http.ptr);
+               if (written != http.len)
+               {
+                       status = FAILED;
+                       break;
+               }
+       }
+       while (status == NEED_MORE);
+
+       xmlFree(xml_str);
+       if (status != SUCCESS)
        {
-               xmlFree(xml);
                return FALSE;
        }
-       xmlFree(xml);
 
-       DBG3(DBG_TNC, "%B", &in);
-       this->doc = xmlParseMemory(in.ptr, in.len);
-       free(in.ptr);
+       /* Receive SOAP-XML response via [chunked] HTTPS */
+       xml = chunk_empty;
+       do
+       {
+               len = this->tls->read(this->tls, buf, sizeof(buf), TRUE);
+               if (len <= 0)
+               {
+                       return FALSE;
+               }
+               http = chunk_create(buf, len);
+
+               status = this->http->process(this->http, &http, &xml);
+               if (status == FAILED)
+               {
+                       free(xml.ptr);
+                       return FALSE;
+               }
+       }
+       while (status == NEED_MORE);
+
+       DBG3(DBG_TNC, "parsing XML message %B", &xml);
+       this->doc = xmlParseMemory(xml.ptr, xml.len);
+       free(xml.ptr);
        
        if (!this->doc)
        {
@@ -309,6 +226,7 @@ METHOD(tnc_ifmap_soap_msg_t, post, bool,
 METHOD(tnc_ifmap_soap_msg_t, destroy, void,
        private_tnc_ifmap_soap_msg_t *this)
 {
+       this->http->destroy(this->http);
        if (this->doc)
        {
                xmlFreeDoc(this->doc);
@@ -329,8 +247,7 @@ tnc_ifmap_soap_msg_t *tnc_ifmap_soap_msg_create(char *uri, chunk_t user_pass,
                        .post = _post,
                        .destroy = _destroy,
                },
-               .uri = uri,
-               .user_pass = user_pass,
+               .http = tnc_ifmap_http_create(uri, user_pass),
                .tls = tls,
        );