summaryrefslogtreecommitdiff
path: root/lib/icap/util.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/icap/util.cpp')
-rw-r--r--lib/icap/util.cpp693
1 files changed, 693 insertions, 0 deletions
diff --git a/lib/icap/util.cpp b/lib/icap/util.cpp
new file mode 100644
index 0000000..c383261
--- /dev/null
+++ b/lib/icap/util.cpp
@@ -0,0 +1,693 @@
+/*
+ * C++ ICAP library
+ * Copyright (C) 2012 Uditha Atukorala
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "util.h"
+
+#include <string>
+#include <algorithm>
+#include <locale>
+#include <iostream>
+
+
+namespace icap {
+
+ namespace util {
+
+ unsigned int hextodec( const std::string &hex ) throw() {
+
+ unsigned int dec;
+ std::stringstream ss;
+
+ ss << std::hex << hex;
+ ss >> dec;
+
+ return dec;
+
+ }
+
+
+ const std::string dectohex( const unsigned int &dec ) throw() {
+
+ std::string hex;
+ std::stringstream ss;
+
+ ss << std::hex << dec;
+ ss >> hex;
+
+ return hex;
+
+ }
+
+
+ int read_line( socketlibrary::TCPSocket * socket, char * buf, int buf_length, bool incl_endl ) throw() {
+
+ int i = 0, n;
+ char c = '\0';
+
+ while ( i < ( buf_length - 1 ) ) {
+
+ n = socket->recv( &c, 1 );
+
+ if ( n > 0 ) {
+ if ( c == '\r' ) {
+
+ if ( incl_endl ) {
+ buf[i] = c;
+ i++;
+ }
+
+ // peak for \n
+ n = socket->peek( &c, 1 );
+
+ if ( ( n > 0 ) && ( c == '\n' ) ) {
+
+ n = socket->recv( &c, 1 );
+
+ if ( incl_endl ) {
+ buf[i] = c;
+ i++;
+ }
+
+ break; // end of line
+ }
+ }
+
+ buf[i] = c;
+ i++;
+ } else {
+ break; // nothing read from socket
+ }
+
+ }
+
+ buf[i] = '\0';
+ return i;
+
+ }
+
+
+ std::string read_line( socketlibrary::TCPSocket * socket, bool incl_endl ) throw() {
+
+ int n;
+ std::string line;
+ char c = '\0';
+
+ try {
+
+ while ( ( n = socket->recv( &c, 1 ) ) > 0 ) {
+
+ if ( c == '\r' ) {
+
+ if ( incl_endl ) {
+ line += c;
+ }
+
+ // peak for \n
+ n = socket->peek( &c, 1 );
+
+ if ( ( n > 0 ) && ( c == '\n' ) ) {
+
+ n = socket->recv( &c, 1 );
+
+ if ( incl_endl ) {
+ line += c;
+ }
+
+ break; // end of line
+ }
+ }
+
+ line += c;
+
+ }
+
+ } catch ( socketlibrary::SocketException &sex ) {
+ // TODO: log error?
+ line = "";
+ }
+
+ return line;
+
+ }
+
+
+ std::string read_data( socketlibrary::TCPSocket * socket, int size ) throw() {
+
+ char buffer[ICAP_BUFFER_SIZE];
+ std::string data = "";
+ int n;
+
+ // loop until we have read all the bytes
+ while ( size > 0 ) {
+
+ try {
+
+ // read from socket
+ n = socket->recv( buffer, min( size, ICAP_BUFFER_SIZE ) );
+
+ // sanity check
+ if ( n == 0 ) {
+ break;
+ }
+
+ // append to data
+ data.append( buffer, n );
+
+ // update size with remaining bytes
+ size -= n;
+
+ } catch ( socketlibrary::SocketException &sex ) {
+ // TODO: log errors ??
+ }
+
+ }
+
+ return data;
+
+ }
+
+
+ unsigned int read_chunk_size( socketlibrary::TCPSocket * socket ) throw() {
+
+ std::string line;
+ std::vector<std::string> chunk_header;
+
+ line = read_line( socket );
+ chunk_header = split( line, ";" );
+
+ return hextodec( chunk_header.at( 0 ) );
+
+ }
+
+
+ void read_chunk_header( socketlibrary::TCPSocket * socket, chunk_t &chunk ) throw() {
+
+ std::string line;
+ std::vector<std::string> chunk_header;
+
+ line = read_line( socket );
+ chunk_header = split( line, ";" );
+
+ // sanity check
+ if ( chunk_header.size() > 0 ) {
+
+ // sanity check
+ if ( chunk_header.at( 0 ).size() > 0 ) {
+ chunk.size = hextodec( chunk_header.at( 0 ) );
+ }
+
+ // check for chunk-extension
+ if ( chunk_header.size() == 2 ) {
+ chunk.extention = trim( chunk_header.at( 1 ) );
+ }
+ }
+
+ return;
+
+ }
+
+
+ chunk_t read_chunk( socketlibrary::TCPSocket * socket ) throw() {
+
+ chunk_t chunk;
+ std::string line;
+ std::vector<std::string> chunk_header;
+
+ // initialise chunk
+ chunk.size = 0;
+ chunk.extention = "";
+ chunk.data = "";
+
+ // read chunk header
+ read_chunk_header( socket, chunk );
+
+ // read chunk data
+ if ( chunk.size > 0 ) {
+ chunk.data = read_data( socket, chunk.size );
+ }
+
+ // read \r\n ending for the chunk
+ read_data( socket, 2 );
+
+ return chunk;
+
+ }
+
+
+ std::string read_chunked( socketlibrary::TCPSocket * socket ) throw() {
+
+ unsigned int chunk_size = 0;
+ unsigned int offset = 0;
+ std::string chunked_data = "";
+
+ while ( ( chunk_size = read_chunk_size( socket ) ) > 0 ) {
+
+ offset = chunked_data.size();
+
+ // read chunk-data
+ chunked_data.append( read_data( socket, chunk_size ) );
+
+ // sanity check
+ if ( ( chunked_data.size() - offset ) != chunk_size ) {
+ // something went wrong
+ break;
+ }
+
+ // extra \r\n
+ read_data( socket, 2 );
+
+ }
+
+ // read until the end of chunked data
+ while ( read_line( socket, true ).size() > 2 ) ;
+
+ return chunked_data;
+
+ }
+
+
+ bool read_chunked_payload( socketlibrary::TCPSocket * socket, std::string &payload ) throw() {
+
+ chunk_t chunk;
+ bool ieof = false;
+
+ do {
+
+ // read chunk
+ chunk = read_chunk( socket );
+
+ // append to payload
+ payload.append( chunk.data );
+
+ // sanity check
+ if ( chunk.data.size() != chunk.size ) {
+ // something went wrong
+ break;
+ }
+
+ } while( chunk.size > 0 );
+
+ // check for ieof
+ if ( chunk.extention == "ieof" ) {
+ ieof = true;
+ }
+
+ return ieof;
+
+ }
+
+
+ bool send_line( const std::string &line, socketlibrary::TCPSocket * socket ) throw() {
+
+ try {
+ socket->send( line.c_str(), line.length() );
+ socket->send( "\r\n", 2 );
+ } catch ( socketlibrary::SocketException &sex ) {
+ // TODO: log errors
+ return false;
+ }
+
+ return true;
+
+ }
+
+
+ bool send_data( const std::string &data, socketlibrary::TCPSocket * socket ) throw() {
+
+ try {
+ socket->send( data.c_str(), data.size() );
+ } catch( socketlibrary::SocketException &sex ) {
+ // TODO: log errors
+ return false;
+ }
+
+ return true;
+
+ }
+
+
+ bool send_chunked( const std::string &data, socketlibrary::TCPSocket * socket ) throw() {
+
+ std::string chunked_data = "";
+ unsigned int offset = 0;
+ int chunks = 0;
+
+ // calculate the number of chunks we need
+ if ( data.size() > ICAP_BUFFER_SIZE ) {
+ chunks = ( data.size() / ICAP_BUFFER_SIZE );
+ }
+
+ try {
+
+ do {
+
+ // prepare data for this chunk
+ chunked_data = data.substr( offset, ICAP_BUFFER_SIZE );
+
+ // sanity check
+ if ( chunked_data.size() <= 0 ) {
+ // we shouldn't get here
+ break;
+ }
+
+ // update offset
+ offset += chunked_data.size();
+
+ // send chunk size
+ if (! send_line( dectohex( chunked_data.size() ), socket ) ) {
+ return false;
+ }
+
+ // send chunk
+ if (! send_data( chunked_data, socket ) ) {
+ return false;
+ }
+
+ chunks--;
+
+ } while ( chunks > 0 );
+
+ // end of chunk
+ if (! send_data( "\r\n0\r\n\r\n", socket ) ) {
+ return false;
+ }
+
+ } catch ( socketlibrary::SocketException &sex ) {
+ // TODO: log errors ??
+ return false;
+ }
+
+ return true;
+
+ }
+
+
+ std::vector<std::string> split( const std::string &str, const std::string &delimiter ) throw() {
+
+ std::vector<std::string> result;
+ size_t current;
+ size_t next = -1;
+
+ do {
+ current = next + 1;
+ next = str.find_first_of( delimiter, current );
+ result.push_back( str.substr( current, ( next - current ) ) );
+ } while ( next != std::string::npos );
+
+ return result;
+
+ }
+
+
+ std::string &ltrim( std::string &str ) throw() {
+ str.erase( str.begin(), std::find_if( str.begin(), str.end(), std::not1( std::ptr_fun<int, int>( std::isspace ) ) ) );
+ return str;
+ }
+
+
+ std::string &rtrim( std::string &str ) throw() {
+ str.erase( std::find_if( str.rbegin(), str.rend(), std::not1( std::ptr_fun<int, int>( std::isspace ) ) ).base(), str.end() );
+ return str;
+ }
+
+
+ std::string &trim( std::string &str ) throw() {
+ return ltrim( rtrim( str ) );
+ }
+
+
+ icap::RequestHeader * read_req_header( socketlibrary::TCPSocket * socket ) throw() {
+
+ char buffer[ICAP_BUFFER_SIZE];
+ int n = 0;
+ std::string data = "";
+
+ while ( ( n = read_line( socket, buffer, ICAP_BUFFER_SIZE, true ) ) > 2 ) {
+ data.append( buffer );
+ }
+
+ icap::RequestHeader * req_header = new icap::RequestHeader( data );
+ return req_header;
+
+ }
+
+
+ bool read_req_data( icap::Request * request, socketlibrary::TCPSocket * socket ) throw() {
+
+ int data_offset = 0;
+ int data_length = 0;
+ int data_read = 0;
+ std::vector<icap::Header::encapsulated_header_data_t> sorted_encaps_header;
+ std::vector<icap::Header::encapsulated_header_data_t>::iterator sorted_idx;
+
+ // payload
+ icap::payload_t payload;
+ payload.req_header = "";
+ payload.req_body = "";
+ payload.res_header = "";
+ payload.res_body = "";
+ payload.ieof = false;
+
+ // header
+ icap::Header * header = request->header();
+ sorted_encaps_header = header->sort_encapsulated_header();
+
+ // loop through the sorted header
+ for ( sorted_idx = sorted_encaps_header.begin(); sorted_idx != sorted_encaps_header.end(); sorted_idx++ ) {
+
+ // don't want to read negative headers
+ if ( sorted_idx->second < 0 ) {
+ continue;
+ }
+
+ // if this is the last header entity then check for chunked content
+ if ( sorted_idx == ( sorted_encaps_header.end() - 1 ) ) {
+
+ if ( sorted_idx->first == "req-body" ) {
+ payload.ieof = read_chunked_payload( socket, payload.req_body );
+ } else if ( sorted_idx->first == "res-body" ) {
+ payload.ieof = read_chunked_payload( socket, payload.res_body );
+ } else {
+ /*
+ * null-body is the only other legal possibility here
+ * we take that into account in the previous iterations
+ */
+ break;
+ }
+
+ } else {
+
+ data_offset = sorted_idx->second;
+ data_length = ( ( sorted_idx + 1 )->second - data_offset );
+
+
+ /* read request data */
+
+ // is there anything to read?
+ if ( data_length > 0 ) {
+
+ // update payload
+ if ( sorted_idx->first == "req-hdr" ) {
+ payload.req_header = read_data( socket, data_length );
+ } else if ( sorted_idx->first == "req-body" ) {
+ payload.req_body = read_data( socket, data_length );
+ } else if ( sorted_idx->first == "res-hdr" ) {
+ payload.res_header = read_data( socket, data_length );
+ } else if ( sorted_idx->first == "res-body" ) {
+ payload.res_body = read_data( socket, data_length );
+ } else {
+ // TODO: error?
+ }
+
+ }
+
+ }
+
+ }
+
+ // update request
+ request->payload( payload );
+
+ return true;
+
+ }
+
+
+ bool read_req_continue_data( icap::Request * request, socketlibrary::TCPSocket * socket ) throw() {
+
+ std::vector<icap::Header::encapsulated_header_data_t> sorted_encaps_header;
+ icap::Header::encapsulated_header_data_t header_idx;
+
+ // copy the payload from request so we can append to it
+ icap::payload_t payload;
+ payload.req_header = request->payload().req_header;
+ payload.req_body = request->payload().req_body;
+ payload.res_header = request->payload().res_header;
+ payload.res_body = request->payload().res_body;
+ payload.ieof = request->payload().ieof;
+
+ // header
+ icap::Header * header = request->header();
+ sorted_encaps_header = header->sort_encapsulated_header();
+
+ // sanity check
+ if ( sorted_encaps_header.size() > 0 ) {
+
+ // we are only interested in the last header entity
+ header_idx = sorted_encaps_header.back();
+
+ // read payload data
+ if ( header_idx.first == "req-body" ) {
+ payload.ieof = read_chunked_payload( socket, payload.req_body );
+ } else if ( header_idx.first == "res-body" ) {
+ payload.ieof = read_chunked_payload( socket, payload.res_body );
+ }
+
+ } else {
+
+ // something isn't quite right
+ return false;
+
+ }
+
+ // update request
+ request->payload( payload );
+
+ return true;
+
+ }
+
+
+ bool send_headers( icap::Header * header, socketlibrary::TCPSocket * socket ) throw() {
+
+ std::string line;
+ icap::Header::headers_index_t i;
+ icap::ResponseHeader::headers_t headers;
+
+ // headers
+ headers = header->headers();
+
+ for ( i = headers.begin(); i != headers.end(); i++ ) {
+
+ line = i->first;
+ line.append( ": " );
+ line.append( i->second );
+
+ if (! send_line( line, socket ) ) {
+ return false;
+ }
+
+ }
+
+ // send encapsulated header
+ line = "Encapsulated: ";
+ line.append( header->encapsulated_header_str() );
+ if (! send_line( line, socket ) ) {
+ return false;
+ }
+
+ // end of header
+ if (! send_data( "\r\n", socket ) ) {
+ return false;
+ }
+
+ return true;
+
+ }
+
+
+ bool send_response( icap::Response * response, socketlibrary::TCPSocket * socket ) throw() {
+
+ bool r_success = true;
+
+ icap::ResponseHeader * header;
+
+ // grab the response header
+ header = response->header();
+
+ // response status
+ std::string line = header->protocol();
+ line.append( " " );
+ line.append( itoa( header->status() ) );
+ line.append( " " );
+ line.append( response_status( header->status() ) );
+
+ r_success = send_line( line, socket );
+
+ // response headers
+ if ( r_success ) {
+ r_success = send_headers( header, socket );
+ }
+
+ // response content (if there are any)
+ if ( r_success ) {
+
+ // req-hdr
+ if ( response->payload().req_header.size() > 0 ) {
+ send_data( response->payload().req_header, socket );
+ }
+
+ // red-body
+ if ( response->payload().req_body.size() > 0 ) {
+ send_chunked( response->payload().req_body, socket );
+ }
+
+ // res-hdr
+ if ( response->payload().res_header.size() > 0 ) {
+ send_data( response->payload().res_header, socket );
+ }
+
+ // res-body
+ if ( response->payload().res_body.size() > 0 ) {
+ send_chunked( response->payload().res_body, socket );
+ }
+
+ }
+
+ return r_success;
+
+ }
+
+
+ const std::string response_status( const ResponseHeader::status_t &status ) throw() {
+
+ // FIXME: probably there's a better way of mapping this
+ std::map<ResponseHeader::status_t, std::string> status_text;
+
+ status_text[ResponseHeader::CONTINUE] = "Continue";
+ status_text[ResponseHeader::OK] = "OK";
+ status_text[ResponseHeader::NO_CONTENT] = "No modifications needed";
+ status_text[ResponseHeader::BAD_REQUEST] = "Bad request";
+ status_text[ResponseHeader::NOT_FOUND] = "ICAP Service not found";
+ status_text[ResponseHeader::NOT_ALLOWED] = "Method not allowed for service";
+ status_text[ResponseHeader::REQ_TIMEOUT] = "Request timeout";
+ status_text[ResponseHeader::SERVER_ERROR] = "Server error";
+ status_text[ResponseHeader::NOT_IMPLEMENTED] = "Method not implemented";
+ status_text[ResponseHeader::BAD_GATEWAY] = "Bad gateway";
+ status_text[ResponseHeader::SERVICE_OVERLOADED] = "Service overloaded";
+ status_text[ResponseHeader::NOT_SUPPORTED] = "ICAP version not supported by server";
+
+ return status_text[status];
+
+ }
+
+ } /* end of namespace util */
+
+} /* end of namespace icap */
+