]> git.ipfire.org Git - thirdparty/opentracker.git/commitdiff
Move http handling to its own sources
authorerdgeist <>
Mon, 3 Dec 2007 00:47:09 +0000 (00:47 +0000)
committererdgeist <>
Mon, 3 Dec 2007 00:47:09 +0000 (00:47 +0000)
ot_http.c [new file with mode: 0644]
ot_http.h [new file with mode: 0644]

diff --git a/ot_http.c b/ot_http.c
new file mode 100644 (file)
index 0000000..0ae14c1
--- /dev/null
+++ b/ot_http.c
@@ -0,0 +1,544 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever. */
+
+/* System */
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Libowfat */
+#include "byte.h"
+#include "array.h"
+#include "iob.h"
+
+/* Opentracker */
+#include "trackerlogic.h"
+#include "ot_mutex.h"
+#include "ot_http.h"
+#include "ot_iovec.h"
+#include "scan_urlencoded_query.h"
+#include "ot_fullscrape.h"
+#include "ot_stats.h"
+#include "ot_accesslist.h"
+#include "ot_sync.h"
+
+#ifndef WANT_TRACKER_SYNC
+#define add_peer_to_torrent(A,B,C) add_peer_to_torrent(A,B)
+#endif
+
+#define OT_MAXMULTISCRAPE_COUNT 64
+static ot_hash multiscrape_buf[OT_MAXMULTISCRAPE_COUNT];
+
+enum {
+  SUCCESS_HTTP_HEADER_LENGTH = 80,
+  SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING = 32,
+  SUCCESS_HTTP_SIZE_OFF = 17 };
+
+/* Our static output buffer */
+static char static_outbuf[8192];
+#ifdef _DEBUG_HTTPERROR
+static char debug_request[8192];
+#endif
+
+static void http_senddata( const int64 client_socket, char *buffer, size_t size ) {
+  struct http_data *h = io_getcookie( client_socket );
+  ssize_t written_size;
+
+  /* whoever sends data is not interested in its input-array */
+  if( h && ( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) ) {
+    h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED;
+    array_reset( &h->request );
+  }
+
+  written_size = write( client_socket, buffer, size );
+  if( ( written_size < 0 ) || ( (size_t)written_size == size ) ) {
+    free( h ); io_close( client_socket );
+  } else {
+    char * outbuf;
+    tai6464 t;
+
+    if( !h ) return;
+    if( !( outbuf =  malloc( size - written_size ) ) ) {
+      free(h); io_close( client_socket );
+      return;
+    }
+
+    iob_reset( &h->batch );
+    memmove( outbuf, buffer + written_size, size - written_size );
+    iob_addbuf_free( &h->batch, outbuf, size - written_size );
+    h->flag |= STRUCT_HTTP_FLAG_IOB_USED;
+
+    /* writeable short data sockets just have a tcp timeout */
+    taia_uint( &t, 0 ); io_timeout( client_socket, t );
+    io_dontwantread( client_socket );
+    io_wantwrite( client_socket );
+  }
+}
+
+#define HTTPERROR_400         return http_issue_error( client_socket, "400 Invalid Request",       "This server only understands GET." )
+#define HTTPERROR_400_PARAM   return http_issue_error( client_socket, "400 Invalid Request",       "Invalid parameter" )
+#define HTTPERROR_400_COMPACT return http_issue_error( client_socket, "400 Invalid Request",       "This server only delivers compact results." )
+#define HTTPERROR_403_IP      return http_issue_error( client_socket, "403 Access Denied",         "Your ip address is not allowed to administrate this server." )
+#define HTTPERROR_404         return http_issue_error( client_socket, "404 Not Found",             "No such file or directory." )
+#define HTTPERROR_500         return http_issue_error( client_socket, "500 Internal Server Error", "A server error has occured. Please retry later." )
+ssize_t http_issue_error( const int64 client_socket, const char *title, const char *message ) {
+  size_t reply_size = sprintf( static_outbuf,
+    "HTTP/1.0 %s\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n",
+    title, strlen(message)+strlen(title)+16-4,title+4);
+#ifdef _DEBUG_HTTPERROR
+  fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request );
+#endif
+  http_senddata( client_socket, static_outbuf, reply_size);
+  return -2;
+}
+
+ssize_t http_sendiovecdata( const int64 client_socket, int iovec_entries, struct iovec *iovector ) {
+  struct http_data *h = io_getcookie( client_socket );
+  char *header;
+  int i;
+  size_t header_size, size = iovec_length( &iovec_entries, &iovector );
+  tai6464 t;
+
+  /* No cookie? Bad socket. Leave. */
+  if( !h ) {
+    iovec_free( &iovec_entries, &iovector );
+    HTTPERROR_500;
+  }
+
+  /* If this socket collected request in a buffer,
+     free it now */
+  if( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) {
+    h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED;
+    array_reset( &h->request );
+  }
+
+  /* If we came here, wait for the answer is over */
+  h->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
+
+  /* Our answers never are 0 vectors. Return an error. */
+  if( !iovec_entries ) {
+    HTTPERROR_500;
+  }
+
+  /* Prepare space for http header */
+  header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING );
+  if( !header ) {
+    iovec_free( &iovec_entries, &iovector );
+    HTTPERROR_500;
+  }
+
+  if( h->flag & STRUCT_HTTP_FLAG_GZIP )
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: gzip\r\nContent-Length: %zd\r\n\r\n", size );
+  else if( h->flag & STRUCT_HTTP_FLAG_BZIP2 )
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Encoding: bzip2\r\nContent-Length: %zd\r\n\r\n", size );
+  else
+    header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size );
+
+  iob_reset( &h->batch );
+  iob_addbuf_free( &h->batch, header, header_size );
+
+  /* Will move to ot_iovec.c */
+  for( i=0; i<iovec_entries; ++i )
+    iob_addbuf_munmap( &h->batch, iovector[i].iov_base, iovector[i].iov_len );
+  free( iovector );
+
+  h->flag |= STRUCT_HTTP_FLAG_IOB_USED;
+
+  /* writeable sockets timeout after twice the pool timeout
+     which defaults to 5 minutes (e.g. after 10 minutes) */
+  taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND );
+  io_timeout( client_socket, t );
+  io_dontwantread( client_socket );
+  io_wantwrite( client_socket );
+  return 0;
+}
+
+#ifdef WANT_TRACKER_SYNC
+static ssize_t http_handle_sync( const int64 client_socket, char *data ) {
+  struct http_data* h = io_getcookie( client_socket );
+  size_t len;
+  int mode = SYNC_OUT, scanon = 1;
+  char *c = data;
+
+  if( !accesslist_isblessed( h->ip, OT_PERMISSION_MAY_SYNC ) )
+    HTTPERROR_403_IP;
+
+  while( scanon ) {
+    switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    default: scan_urlencoded_skipvalue( &c ); break;
+    case 9:
+      if(byte_diff(data,9,"changeset")) {
+        scan_urlencoded_skipvalue( &c );
+        continue;
+      }
+      /* ignore this, when we dont at least see "d4:syncdee" */
+      if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) < 10 ) HTTPERROR_400_PARAM;
+      if( add_changeset_to_tracker( (uint8_t*)data, len ) ) HTTPERROR_400_PARAM;
+      if( mode == SYNC_OUT ) {
+        stats_issue_event( EVENT_SYNC_IN, 1, 0 );
+        mode = SYNC_IN;
+      }
+      break;
+    }
+  }
+
+  if( mode == SYNC_OUT ) {
+    /* Pass this task to the worker thread */
+    h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
+    stats_issue_event( EVENT_SYNC_OUT_REQUEST, 1, 0 );
+    sync_deliver( client_socket );
+    io_dontwantread( client_socket );
+    return -2;
+  }
+
+  /* Simple but proof for now */
+  memmove( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "OK", 2);
+  return 2;
+}
+#endif
+
+static ssize_t http_handle_stats( const int64 client_socket, char *data, char *d, size_t l ) {
+  struct http_data* h = io_getcookie( client_socket );
+  char *c = data;
+  int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
+
+  while( scanon ) {
+    switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    default: scan_urlencoded_skipvalue( &c ); break;
+    case 4:
+      if( byte_diff(data,4,"mode")) {
+        scan_urlencoded_skipvalue( &c );
+        continue;
+      }
+      if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 4 ) HTTPERROR_400_PARAM;
+      if( !byte_diff(data,4,"peer"))
+        mode = TASK_STATS_PEERS;
+      else if( !byte_diff(data,4,"conn"))
+        mode = TASK_STATS_CONNS;
+      else if( !byte_diff(data,4,"top5"))
+        mode = TASK_STATS_TOP5;
+      else if( !byte_diff(data,4,"scrp"))
+             mode = TASK_STATS_SCRAPE;
+      else if( !byte_diff(data,4,"fscr"))
+        mode = TASK_STATS_FULLSCRAPE;
+      else if( !byte_diff(data,4,"tcp4"))
+        mode = TASK_STATS_TCP;
+      else if( !byte_diff(data,4,"udp4"))
+        mode = TASK_STATS_UDP;
+      else if( !byte_diff(data,4,"s24s"))
+        mode = TASK_STATS_SLASH24S;
+      else if( !byte_diff(data,4,"tpbs"))
+        mode = TASK_STATS_TPB;
+      else
+        HTTPERROR_400_PARAM;
+      break;
+    case 6:
+      if( byte_diff(data,6,"format")) {
+        scan_urlencoded_skipvalue( &c );
+        continue;
+      }
+      if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 3 ) HTTPERROR_400_PARAM;
+      if( !byte_diff(data,3,"bin"))
+        format = TASK_FULLSCRAPE_TPB_BINARY;
+      else if( !byte_diff(data,3,"ben"))
+        format = TASK_FULLSCRAPE;
+      else if( !byte_diff(data,3,"url"))
+        format = TASK_FULLSCRAPE_TPB_URLENCODED;
+      else if( !byte_diff(data,3,"txt"))
+        format = TASK_FULLSCRAPE_TPB_ASCII;
+      else
+        HTTPERROR_400_PARAM;
+      break;
+    }
+  }
+
+  if( mode == TASK_STATS_TPB ) {
+    tai6464 t;
+#ifdef WANT_COMPRESSION_GZIP
+    d[l-1] = 0;
+    if( strstr( d, "gzip" ) ) {
+      h->flag |= STRUCT_HTTP_FLAG_GZIP;
+      format |= TASK_FLAG_GZIP;
+    }
+#endif
+    /* Pass this task to the worker thread */
+    h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
+
+    /* Clients waiting for us should not easily timeout */
+    taia_uint( &t, 0 ); io_timeout( client_socket, t );
+    fullscrape_deliver( client_socket, format );
+    io_dontwantread( client_socket );
+    return -2;
+  }
+
+  // default format for now
+  if( !( l = return_stats_for_tracker( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, mode, 0 ) ) ) HTTPERROR_500;
+  return l;
+}
+
+static ssize_t http_handle_fullscrape( const int64 client_socket, char *d, size_t l ) {
+  struct http_data* h = io_getcookie( client_socket );
+  int format = 0;
+  tai6464 t;
+
+#ifdef WANT_COMPRESSION_GZIP
+  d[l-1] = 0;
+  if( strstr( d, "gzip" ) ) {
+    h->flag |= STRUCT_HTTP_FLAG_GZIP;
+    format = TASK_FLAG_GZIP;
+    stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP, *(int*)h->ip, 0 );
+  } else
+#endif
+    stats_issue_event( EVENT_FULLSCRAPE_REQUEST, *(int*)h->ip, 0 );
+
+#ifdef _DEBUG_HTTPERROR
+write( 2, debug_request, l );
+#endif
+
+  /* Pass this task to the worker thread */
+  h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
+  /* Clients waiting for us should not easily timeout */
+  taia_uint( &t, 0 ); io_timeout( client_socket, t );
+  fullscrape_deliver( client_socket, TASK_FULLSCRAPE | format );
+  io_dontwantread( client_socket );
+  return -2;
+}
+
+static ssize_t http_handle_scrape( const int64 client_socket, char *data ) {
+  int scanon = 1, numwant = 0;
+  char *c = data;
+  size_t l;
+
+  /* This is to hack around stupid clients that send "scrape ?info_hash" */
+  if( c[-1] != '?' ) {
+    while( ( *c != '?' ) && ( *c != '\n' ) ) ++c;
+    if( *c == '\n' ) HTTPERROR_400_PARAM;
+    ++c;
+  }
+
+  while( scanon ) {
+    switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1:
+    if( numwant )
+        goto UTORRENT1600_WORKAROUND;
+      HTTPERROR_400_PARAM; /* PARSE ERROR */
+    default: scan_urlencoded_skipvalue( &c ); break;
+    case 9:
+      if(byte_diff(data,9,"info_hash")) {
+        scan_urlencoded_skipvalue( &c );
+        continue;
+      }
+      /* ignore this, when we have less than 20 bytes */
+      if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) ) {
+#ifdef WANT_UTORRENT1600_WORKAROUND
+        if( data[20] != '?' )
+#endif
+        HTTPERROR_400_PARAM;
+      }
+      if( numwant < OT_MAXMULTISCRAPE_COUNT )
+        memmove( multiscrape_buf + numwant++, data, sizeof(ot_hash) );
+      break;
+    }
+  }
+
+UTORRENT1600_WORKAROUND:
+
+  /* No info_hash found? Inform user */
+  if( !numwant ) HTTPERROR_400_PARAM;
+
+  /* Enough for http header + whole scrape string */
+  if( !( l = return_tcp_scrape_for_torrent( multiscrape_buf, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) ) HTTPERROR_500;
+  stats_issue_event( EVENT_SCRAPE, 1, l );
+  return l;
+}
+
+static ssize_t http_handle_announce( const int64 client_socket, char *data ) {
+  char       *c = data;
+  int         numwant, tmp, scanon;
+  ot_peer     peer;
+  ot_torrent *torrent;
+  ot_hash    *hash = NULL;
+  unsigned short port = htons(6881);
+  ssize_t     len;
+
+  /* This is to hack around stupid clients that send "announce ?info_hash" */
+  if( c[-1] != '?' ) {
+    while( ( *c != '?' ) && ( *c != '\n' ) ) ++c;
+    if( *c == '\n' ) HTTPERROR_400_PARAM;
+    ++c;
+  }
+
+  OT_SETIP( &peer, ((struct http_data*)io_getcookie( client_socket ) )->ip );
+  OT_SETPORT( &peer, &port );
+  OT_FLAG( &peer ) = 0;
+  numwant = 50;
+  scanon = 1;
+
+  while( scanon ) {
+    switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
+    case -2: scanon = 0; break;   /* TERMINATOR */
+    case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
+    default: scan_urlencoded_skipvalue( &c ); break;
+#ifdef WANT_IP_FROM_QUERY_STRING
+    case 2:
+      if(!byte_diff(data,2,"ip")) {
+        unsigned char ip[4];
+        len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
+        if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) HTTPERROR_400_PARAM;
+        OT_SETIP( &peer, ip );
+     } else
+        scan_urlencoded_skipvalue( &c );
+     break;
+#endif
+    case 4:
+      if( !byte_diff( data, 4, "port" ) ) {
+        len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
+        if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM;
+        port = htons( tmp ); OT_SETPORT( &peer, &port );
+      } else if( !byte_diff( data, 4, "left" ) ) {
+        if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
+        if( scan_fixed_int( data, len, &tmp ) ) tmp = 0;
+        if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING;
+      } else
+        scan_urlencoded_skipvalue( &c );
+      break;
+    case 5:
+      if( byte_diff( data, 5, "event" ) )
+        scan_urlencoded_skipvalue( &c );
+      else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) {
+      case -1:
+        HTTPERROR_400_PARAM;
+      case 7:
+        if( !byte_diff( data, 7, "stopped" ) ) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED;
+        break;
+      case 9:
+        if( !byte_diff( data, 9, "completed" ) ) OT_FLAG( &peer ) |= PEER_FLAG_COMPLETED;
+      default: /* Fall through intended */
+        break;
+      }
+      break;
+    case 7:
+      if(!byte_diff(data,7,"numwant")) {
+        len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
+        if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) HTTPERROR_400_PARAM;
+        if( numwant < 0 ) numwant = 50;
+        if( numwant > 200 ) numwant = 200;
+      } else if(!byte_diff(data,7,"compact")) {
+        len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
+        if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) HTTPERROR_400_PARAM;
+        if( !tmp ) HTTPERROR_400_COMPACT;
+      } else
+        scan_urlencoded_skipvalue( &c );
+      break;
+    case 9:
+      if(byte_diff(data,9,"info_hash")) {
+        scan_urlencoded_skipvalue( &c );
+        continue;
+      }
+      /* ignore this, when we have less than 20 bytes */
+      if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
+      hash = (ot_hash*)data;
+      break;
+    }
+  }
+
+  /* Scanned whole query string */
+  if( !hash )
+    return sprintf( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "d14:failure reason81:Your client forgot to send your torrent's info_hash. Please upgrade your client.e" );
+
+  if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED )
+    len = remove_peer_from_torrent( hash, &peer, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 );
+  else {
+    torrent = add_peer_to_torrent( hash, &peer, 0 );
+    if( !torrent || !( len = return_peers_for_torrent( hash, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ) ) ) HTTPERROR_500;
+  }
+  stats_issue_event( EVENT_ANNOUNCE, 1, len);
+  return len;
+}
+
+ssize_t http_handle_request( const int64 client_socket, char *data, size_t recv_length ) {
+  char       *c, *recv_header=data;
+  ssize_t     reply_size = 0, reply_off, len;
+
+#ifdef _DEBUG_HTTPERROR
+  if( recv_length >= sizeof( debug_request ) )
+    recv_length = sizeof( debug_request) - 1;
+  memcpy( debug_request, recv_header, recv_length );
+  debug_request[ recv_length ] = 0;
+#endif
+
+  /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
+  if( byte_diff( data, 5, "GET /") ) HTTPERROR_400;
+
+  /* Query string MUST terminate with SP -- we know that theres at least a '\n' where this search terminates */
+  for( c = data + 5; *c!=' ' && *c != '\t' && *c != '\n' && *c != '\r'; ++c ) ;
+  if( *c != ' ' ) HTTPERROR_400;
+
+  /* Skip leading '/' */
+  for( c = data+4; *c == '/'; ++c);
+
+  /* Try to parse the request.
+     In reality we abandoned requiring the url to be correct. This now
+     only decodes url encoded characters, we check for announces and
+     scrapes by looking for "a*" or "sc" */
+  len = scan_urlencoded_query( &c, data = c, SCAN_PATH );
+
+  /* If parsing returned an error, leave with not found*/
+  if( len <= 0 ) HTTPERROR_404;
+
+  /* This is the hardcore match for announce*/
+  if( ( *data == 'a' ) || ( *data == '?' ) )
+    reply_size = http_handle_announce( client_socket, c );
+  else if( !byte_diff( data, 12, "scrape HTTP/" ) )
+    reply_size = http_handle_fullscrape( client_socket, recv_header, recv_length );
+  /* This is the hardcore match for scrape */
+  else if( !byte_diff( data, 2, "sc" ) )
+    reply_size = http_handle_scrape( client_socket, c );
+  /* All the rest is matched the standard way */
+  else switch( len ) {
+#ifdef WANT_TRACKER_SYNC
+  case 4: /* sync ? */
+    if( byte_diff( data, 4, "sync") ) HTTPERROR_404;
+    reply_size = http_handle_sync( client_socket, c );
+    break;
+#endif
+  case 5: /* stats ? */
+    if( byte_diff(data,5,"stats")) HTTPERROR_404;
+    reply_size = http_handle_stats( client_socket, c, recv_header, recv_length );
+    break;
+  default:
+    HTTPERROR_404;
+  }
+
+  /* If routines handled sending themselves, just return */
+  if( reply_size == -2 ) return 0;
+  /* If routine failed, let http error take over */
+  if( reply_size == -1 ) HTTPERROR_500;
+
+  /* This one is rather ugly, so I take you step by step through it.
+
+     1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
+     write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string
+     plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
+     the space NOT needed to expand in reply_off
+  */
+  reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_outbuf, 0, "%zd", reply_size );
+
+  /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
+     packet size is increased by size of header plus one byte '\n', we  will copy over '\0' in next step */
+  reply_size += 1 + sprintf( static_outbuf + reply_off, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", reply_size );
+
+  /* 3. Finally we join both blocks neatly */
+  static_outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n';
+
+  http_senddata( client_socket, static_outbuf + reply_off, reply_size );
+  return reply_size;
+}
diff --git a/ot_http.h b/ot_http.h
new file mode 100644 (file)
index 0000000..84b9028
--- /dev/null
+++ b/ot_http.h
@@ -0,0 +1,28 @@
+/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
+   It is considered beerware. Prost. Skol. Cheers or whatever. */
+
+#ifndef __OT_HTTP_H__
+#define __OT_HTTP_H__
+
+typedef enum {
+  STRUCT_HTTP_FLAG_ARRAY_USED     = 1,
+  STRUCT_HTTP_FLAG_IOB_USED       = 2,
+  STRUCT_HTTP_FLAG_WAITINGFORTASK = 4,
+  STRUCT_HTTP_FLAG_GZIP           = 8,
+  STRUCT_HTTP_FLAG_BZIP2          = 16
+} STRUCT_HTTP_FLAG;
+
+struct http_data {
+  union {
+    array          request;
+    io_batch       batch;
+  };
+  char             ip[4];
+  STRUCT_HTTP_FLAG flag;
+};
+
+ssize_t http_handle_request( const int64 s, char *data, size_t l );
+ssize_t http_sendiovecdata( const int64 s, int iovec_entries, struct iovec *iovector );
+ssize_t http_issue_error( const int64 s, const char *title, const char *message );
+
+#endif
\ No newline at end of file