support chunked HTTP responses

This commit is contained in:
Andreas Steffen
2013-04-03 21:38:04 +02:00
parent 1044710b04
commit 91503c2112
4 changed files with 370 additions and 140 deletions
@@ -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
@@ -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;
}
@@ -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_ @}*/
@@ -13,14 +13,10 @@
* 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
* HTTP POST request builder and response processing
*/
char *uri;
tnc_ifmap_http_t *http;
/**
* Optional base64-encoded username:password for HTTP Basic Authentication
*/
chunk_t user_pass;
/**
* TLS Socket
* TLS socket
*/
tls_socket_t *tls;
@@ -58,118 +49,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
*/
@@ -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,
);