summaryrefslogtreecommitdiff
path: root/ot_http.c
diff options
context:
space:
mode:
Diffstat (limited to 'ot_http.c')
-rw-r--r--ot_http.c544
1 files changed, 544 insertions, 0 deletions
diff --git a/ot_http.c b/ot_http.c
new file mode 100644
index 0000000..0ae14c1
--- /dev/null
+++ b/ot_http.c
@@ -0,0 +1,544 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <sys/types.h>
6#include <sys/uio.h>
7#include <stdlib.h>
8#include <stdio.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Libowfat */
13#include "byte.h"
14#include "array.h"
15#include "iob.h"
16
17/* Opentracker */
18#include "trackerlogic.h"
19#include "ot_mutex.h"
20#include "ot_http.h"
21#include "ot_iovec.h"
22#include "scan_urlencoded_query.h"
23#include "ot_fullscrape.h"
24#include "ot_stats.h"
25#include "ot_accesslist.h"
26#include "ot_sync.h"
27
28#ifndef WANT_TRACKER_SYNC
29#define add_peer_to_torrent(A,B,C) add_peer_to_torrent(A,B)
30#endif
31
32#define OT_MAXMULTISCRAPE_COUNT 64
33static ot_hash multiscrape_buf[OT_MAXMULTISCRAPE_COUNT];
34
35enum {
36 SUCCESS_HTTP_HEADER_LENGTH = 80,
37 SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING = 32,
38 SUCCESS_HTTP_SIZE_OFF = 17 };
39
40/* Our static output buffer */
41static char static_outbuf[8192];
42#ifdef _DEBUG_HTTPERROR
43static char debug_request[8192];
44#endif
45
46static void http_senddata( const int64 client_socket, char *buffer, size_t size ) {
47 struct http_data *h = io_getcookie( client_socket );
48 ssize_t written_size;
49
50 /* whoever sends data is not interested in its input-array */
51 if( h && ( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) ) {
52 h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED;
53 array_reset( &h->request );
54 }
55
56 written_size = write( client_socket, buffer, size );
57 if( ( written_size < 0 ) || ( (size_t)written_size == size ) ) {
58 free( h ); io_close( client_socket );
59 } else {
60 char * outbuf;
61 tai6464 t;
62
63 if( !h ) return;
64 if( !( outbuf = malloc( size - written_size ) ) ) {
65 free(h); io_close( client_socket );
66 return;
67 }
68
69 iob_reset( &h->batch );
70 memmove( outbuf, buffer + written_size, size - written_size );
71 iob_addbuf_free( &h->batch, outbuf, size - written_size );
72 h->flag |= STRUCT_HTTP_FLAG_IOB_USED;
73
74 /* writeable short data sockets just have a tcp timeout */
75 taia_uint( &t, 0 ); io_timeout( client_socket, t );
76 io_dontwantread( client_socket );
77 io_wantwrite( client_socket );
78 }
79}
80
81#define HTTPERROR_400 return http_issue_error( client_socket, "400 Invalid Request", "This server only understands GET." )
82#define HTTPERROR_400_PARAM return http_issue_error( client_socket, "400 Invalid Request", "Invalid parameter" )
83#define HTTPERROR_400_COMPACT return http_issue_error( client_socket, "400 Invalid Request", "This server only delivers compact results." )
84#define HTTPERROR_403_IP return http_issue_error( client_socket, "403 Access Denied", "Your ip address is not allowed to administrate this server." )
85#define HTTPERROR_404 return http_issue_error( client_socket, "404 Not Found", "No such file or directory." )
86#define HTTPERROR_500 return http_issue_error( client_socket, "500 Internal Server Error", "A server error has occured. Please retry later." )
87ssize_t http_issue_error( const int64 client_socket, const char *title, const char *message ) {
88 size_t reply_size = sprintf( static_outbuf,
89 "HTTP/1.0 %s\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n",
90 title, strlen(message)+strlen(title)+16-4,title+4);
91#ifdef _DEBUG_HTTPERROR
92 fprintf( stderr, "DEBUG: invalid request was: %s\n", debug_request );
93#endif
94 http_senddata( client_socket, static_outbuf, reply_size);
95 return -2;
96}
97
98ssize_t http_sendiovecdata( const int64 client_socket, int iovec_entries, struct iovec *iovector ) {
99 struct http_data *h = io_getcookie( client_socket );
100 char *header;
101 int i;
102 size_t header_size, size = iovec_length( &iovec_entries, &iovector );
103 tai6464 t;
104
105 /* No cookie? Bad socket. Leave. */
106 if( !h ) {
107 iovec_free( &iovec_entries, &iovector );
108 HTTPERROR_500;
109 }
110
111 /* If this socket collected request in a buffer,
112 free it now */
113 if( h->flag & STRUCT_HTTP_FLAG_ARRAY_USED ) {
114 h->flag &= ~STRUCT_HTTP_FLAG_ARRAY_USED;
115 array_reset( &h->request );
116 }
117
118 /* If we came here, wait for the answer is over */
119 h->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
120
121 /* Our answers never are 0 vectors. Return an error. */
122 if( !iovec_entries ) {
123 HTTPERROR_500;
124 }
125
126 /* Prepare space for http header */
127 header = malloc( SUCCESS_HTTP_HEADER_LENGTH + SUCCESS_HTTP_HEADER_LENGHT_CONTENT_ENCODING );
128 if( !header ) {
129 iovec_free( &iovec_entries, &iovector );
130 HTTPERROR_500;
131 }
132
133 if( h->flag & STRUCT_HTTP_FLAG_GZIP )
134 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 );
135 else if( h->flag & STRUCT_HTTP_FLAG_BZIP2 )
136 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 );
137 else
138 header_size = sprintf( header, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r\n", size );
139
140 iob_reset( &h->batch );
141 iob_addbuf_free( &h->batch, header, header_size );
142
143 /* Will move to ot_iovec.c */
144 for( i=0; i<iovec_entries; ++i )
145 iob_addbuf_munmap( &h->batch, iovector[i].iov_base, iovector[i].iov_len );
146 free( iovector );
147
148 h->flag |= STRUCT_HTTP_FLAG_IOB_USED;
149
150 /* writeable sockets timeout after twice the pool timeout
151 which defaults to 5 minutes (e.g. after 10 minutes) */
152 taia_now( &t ); taia_addsec( &t, &t, OT_CLIENT_TIMEOUT_SEND );
153 io_timeout( client_socket, t );
154 io_dontwantread( client_socket );
155 io_wantwrite( client_socket );
156 return 0;
157}
158
159#ifdef WANT_TRACKER_SYNC
160static ssize_t http_handle_sync( const int64 client_socket, char *data ) {
161 struct http_data* h = io_getcookie( client_socket );
162 size_t len;
163 int mode = SYNC_OUT, scanon = 1;
164 char *c = data;
165
166 if( !accesslist_isblessed( h->ip, OT_PERMISSION_MAY_SYNC ) )
167 HTTPERROR_403_IP;
168
169 while( scanon ) {
170 switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
171 case -2: scanon = 0; break; /* TERMINATOR */
172 case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
173 default: scan_urlencoded_skipvalue( &c ); break;
174 case 9:
175 if(byte_diff(data,9,"changeset")) {
176 scan_urlencoded_skipvalue( &c );
177 continue;
178 }
179 /* ignore this, when we dont at least see "d4:syncdee" */
180 if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) < 10 ) HTTPERROR_400_PARAM;
181 if( add_changeset_to_tracker( (uint8_t*)data, len ) ) HTTPERROR_400_PARAM;
182 if( mode == SYNC_OUT ) {
183 stats_issue_event( EVENT_SYNC_IN, 1, 0 );
184 mode = SYNC_IN;
185 }
186 break;
187 }
188 }
189
190 if( mode == SYNC_OUT ) {
191 /* Pass this task to the worker thread */
192 h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
193 stats_issue_event( EVENT_SYNC_OUT_REQUEST, 1, 0 );
194 sync_deliver( client_socket );
195 io_dontwantread( client_socket );
196 return -2;
197 }
198
199 /* Simple but proof for now */
200 memmove( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, "OK", 2);
201 return 2;
202}
203#endif
204
205static ssize_t http_handle_stats( const int64 client_socket, char *data, char *d, size_t l ) {
206 struct http_data* h = io_getcookie( client_socket );
207 char *c = data;
208 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
209
210 while( scanon ) {
211 switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
212 case -2: scanon = 0; break; /* TERMINATOR */
213 case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
214 default: scan_urlencoded_skipvalue( &c ); break;
215 case 4:
216 if( byte_diff(data,4,"mode")) {
217 scan_urlencoded_skipvalue( &c );
218 continue;
219 }
220 if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 4 ) HTTPERROR_400_PARAM;
221 if( !byte_diff(data,4,"peer"))
222 mode = TASK_STATS_PEERS;
223 else if( !byte_diff(data,4,"conn"))
224 mode = TASK_STATS_CONNS;
225 else if( !byte_diff(data,4,"top5"))
226 mode = TASK_STATS_TOP5;
227 else if( !byte_diff(data,4,"scrp"))
228 mode = TASK_STATS_SCRAPE;
229 else if( !byte_diff(data,4,"fscr"))
230 mode = TASK_STATS_FULLSCRAPE;
231 else if( !byte_diff(data,4,"tcp4"))
232 mode = TASK_STATS_TCP;
233 else if( !byte_diff(data,4,"udp4"))
234 mode = TASK_STATS_UDP;
235 else if( !byte_diff(data,4,"s24s"))
236 mode = TASK_STATS_SLASH24S;
237 else if( !byte_diff(data,4,"tpbs"))
238 mode = TASK_STATS_TPB;
239 else
240 HTTPERROR_400_PARAM;
241 break;
242 case 6:
243 if( byte_diff(data,6,"format")) {
244 scan_urlencoded_skipvalue( &c );
245 continue;
246 }
247 if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 3 ) HTTPERROR_400_PARAM;
248 if( !byte_diff(data,3,"bin"))
249 format = TASK_FULLSCRAPE_TPB_BINARY;
250 else if( !byte_diff(data,3,"ben"))
251 format = TASK_FULLSCRAPE;
252 else if( !byte_diff(data,3,"url"))
253 format = TASK_FULLSCRAPE_TPB_URLENCODED;
254 else if( !byte_diff(data,3,"txt"))
255 format = TASK_FULLSCRAPE_TPB_ASCII;
256 else
257 HTTPERROR_400_PARAM;
258 break;
259 }
260 }
261
262 if( mode == TASK_STATS_TPB ) {
263 tai6464 t;
264#ifdef WANT_COMPRESSION_GZIP
265 d[l-1] = 0;
266 if( strstr( d, "gzip" ) ) {
267 h->flag |= STRUCT_HTTP_FLAG_GZIP;
268 format |= TASK_FLAG_GZIP;
269 }
270#endif
271 /* Pass this task to the worker thread */
272 h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
273
274 /* Clients waiting for us should not easily timeout */
275 taia_uint( &t, 0 ); io_timeout( client_socket, t );
276 fullscrape_deliver( client_socket, format );
277 io_dontwantread( client_socket );
278 return -2;
279 }
280
281 // default format for now
282 if( !( l = return_stats_for_tracker( static_outbuf + SUCCESS_HTTP_HEADER_LENGTH, mode, 0 ) ) ) HTTPERROR_500;
283 return l;
284}
285
286static ssize_t http_handle_fullscrape( const int64 client_socket, char *d, size_t l ) {
287 struct http_data* h = io_getcookie( client_socket );
288 int format = 0;
289 tai6464 t;
290
291#ifdef WANT_COMPRESSION_GZIP
292 d[l-1] = 0;
293 if( strstr( d, "gzip" ) ) {
294 h->flag |= STRUCT_HTTP_FLAG_GZIP;
295 format = TASK_FLAG_GZIP;
296 stats_issue_event( EVENT_FULLSCRAPE_REQUEST_GZIP, *(int*)h->ip, 0 );
297 } else
298#endif
299 stats_issue_event( EVENT_FULLSCRAPE_REQUEST, *(int*)h->ip, 0 );
300
301#ifdef _DEBUG_HTTPERROR
302write( 2, debug_request, l );
303#endif
304
305 /* Pass this task to the worker thread */
306 h->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
307 /* Clients waiting for us should not easily timeout */
308 taia_uint( &t, 0 ); io_timeout( client_socket, t );
309 fullscrape_deliver( client_socket, TASK_FULLSCRAPE | format );
310 io_dontwantread( client_socket );
311 return -2;
312}
313
314static ssize_t http_handle_scrape( const int64 client_socket, char *data ) {
315 int scanon = 1, numwant = 0;
316 char *c = data;
317 size_t l;
318
319 /* This is to hack around stupid clients that send "scrape ?info_hash" */
320 if( c[-1] != '?' ) {
321 while( ( *c != '?' ) && ( *c != '\n' ) ) ++c;
322 if( *c == '\n' ) HTTPERROR_400_PARAM;
323 ++c;
324 }
325
326 while( scanon ) {
327 switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
328 case -2: scanon = 0; break; /* TERMINATOR */
329 case -1:
330 if( numwant )
331 goto UTORRENT1600_WORKAROUND;
332 HTTPERROR_400_PARAM; /* PARSE ERROR */
333 default: scan_urlencoded_skipvalue( &c ); break;
334 case 9:
335 if(byte_diff(data,9,"info_hash")) {
336 scan_urlencoded_skipvalue( &c );
337 continue;
338 }
339 /* ignore this, when we have less than 20 bytes */
340 if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != (ssize_t)sizeof(ot_hash) ) {
341#ifdef WANT_UTORRENT1600_WORKAROUND
342 if( data[20] != '?' )
343#endif
344 HTTPERROR_400_PARAM;
345 }
346 if( numwant < OT_MAXMULTISCRAPE_COUNT )
347 memmove( multiscrape_buf + numwant++, data, sizeof(ot_hash) );
348 break;
349 }
350 }
351
352UTORRENT1600_WORKAROUND:
353
354 /* No info_hash found? Inform user */
355 if( !numwant ) HTTPERROR_400_PARAM;
356
357 /* Enough for http header + whole scrape string */
358 if( !( l = return_tcp_scrape_for_torrent( multiscrape_buf, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf ) ) ) HTTPERROR_500;
359 stats_issue_event( EVENT_SCRAPE, 1, l );
360 return l;
361}
362
363static ssize_t http_handle_announce( const int64 client_socket, char *data ) {
364 char *c = data;
365 int numwant, tmp, scanon;
366 ot_peer peer;
367 ot_torrent *torrent;
368 ot_hash *hash = NULL;
369 unsigned short port = htons(6881);
370 ssize_t len;
371
372 /* This is to hack around stupid clients that send "announce ?info_hash" */
373 if( c[-1] != '?' ) {
374 while( ( *c != '?' ) && ( *c != '\n' ) ) ++c;
375 if( *c == '\n' ) HTTPERROR_400_PARAM;
376 ++c;
377 }
378
379 OT_SETIP( &peer, ((struct http_data*)io_getcookie( client_socket ) )->ip );
380 OT_SETPORT( &peer, &port );
381 OT_FLAG( &peer ) = 0;
382 numwant = 50;
383 scanon = 1;
384
385 while( scanon ) {
386 switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_PARAM ) ) {
387 case -2: scanon = 0; break; /* TERMINATOR */
388 case -1: HTTPERROR_400_PARAM; /* PARSE ERROR */
389 default: scan_urlencoded_skipvalue( &c ); break;
390#ifdef WANT_IP_FROM_QUERY_STRING
391 case 2:
392 if(!byte_diff(data,2,"ip")) {
393 unsigned char ip[4];
394 len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
395 if( ( len <= 0 ) || scan_fixed_ip( data, len, ip ) ) HTTPERROR_400_PARAM;
396 OT_SETIP( &peer, ip );
397 } else
398 scan_urlencoded_skipvalue( &c );
399 break;
400#endif
401 case 4:
402 if( !byte_diff( data, 4, "port" ) ) {
403 len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
404 if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) || ( tmp > 0xffff ) ) HTTPERROR_400_PARAM;
405 port = htons( tmp ); OT_SETPORT( &peer, &port );
406 } else if( !byte_diff( data, 4, "left" ) ) {
407 if( ( len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) <= 0 ) HTTPERROR_400_PARAM;
408 if( scan_fixed_int( data, len, &tmp ) ) tmp = 0;
409 if( !tmp ) OT_FLAG( &peer ) |= PEER_FLAG_SEEDING;
410 } else
411 scan_urlencoded_skipvalue( &c );
412 break;
413 case 5:
414 if( byte_diff( data, 5, "event" ) )
415 scan_urlencoded_skipvalue( &c );
416 else switch( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) ) {
417 case -1:
418 HTTPERROR_400_PARAM;
419 case 7:
420 if( !byte_diff( data, 7, "stopped" ) ) OT_FLAG( &peer ) |= PEER_FLAG_STOPPED;
421 break;
422 case 9:
423 if( !byte_diff( data, 9, "completed" ) ) OT_FLAG( &peer ) |= PEER_FLAG_COMPLETED;
424 default: /* Fall through intended */
425 break;
426 }
427 break;
428 case 7:
429 if(!byte_diff(data,7,"numwant")) {
430 len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
431 if( ( len <= 0 ) || scan_fixed_int( data, len, &numwant ) ) HTTPERROR_400_PARAM;
432 if( numwant < 0 ) numwant = 50;
433 if( numwant > 200 ) numwant = 200;
434 } else if(!byte_diff(data,7,"compact")) {
435 len = scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE );
436 if( ( len <= 0 ) || scan_fixed_int( data, len, &tmp ) ) HTTPERROR_400_PARAM;
437 if( !tmp ) HTTPERROR_400_COMPACT;
438 } else
439 scan_urlencoded_skipvalue( &c );
440 break;
441 case 9:
442 if(byte_diff(data,9,"info_hash")) {
443 scan_urlencoded_skipvalue( &c );
444 continue;
445 }
446 /* ignore this, when we have less than 20 bytes */
447 if( scan_urlencoded_query( &c, data = c, SCAN_SEARCHPATH_VALUE ) != 20 ) HTTPERROR_400_PARAM;
448 hash = (ot_hash*)data;
449 break;
450 }
451 }
452
453 /* Scanned whole query string */
454 if( !hash )
455 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" );
456
457 if( OT_FLAG( &peer ) & PEER_FLAG_STOPPED )
458 len = remove_peer_from_torrent( hash, &peer, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 );
459 else {
460 torrent = add_peer_to_torrent( hash, &peer, 0 );
461 if( !torrent || !( len = return_peers_for_torrent( hash, numwant, SUCCESS_HTTP_HEADER_LENGTH + static_outbuf, 1 ) ) ) HTTPERROR_500;
462 }
463 stats_issue_event( EVENT_ANNOUNCE, 1, len);
464 return len;
465}
466
467ssize_t http_handle_request( const int64 client_socket, char *data, size_t recv_length ) {
468 char *c, *recv_header=data;
469 ssize_t reply_size = 0, reply_off, len;
470
471#ifdef _DEBUG_HTTPERROR
472 if( recv_length >= sizeof( debug_request ) )
473 recv_length = sizeof( debug_request) - 1;
474 memcpy( debug_request, recv_header, recv_length );
475 debug_request[ recv_length ] = 0;
476#endif
477
478 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
479 if( byte_diff( data, 5, "GET /") ) HTTPERROR_400;
480
481 /* Query string MUST terminate with SP -- we know that theres at least a '\n' where this search terminates */
482 for( c = data + 5; *c!=' ' && *c != '\t' && *c != '\n' && *c != '\r'; ++c ) ;
483 if( *c != ' ' ) HTTPERROR_400;
484
485 /* Skip leading '/' */
486 for( c = data+4; *c == '/'; ++c);
487
488 /* Try to parse the request.
489 In reality we abandoned requiring the url to be correct. This now
490 only decodes url encoded characters, we check for announces and
491 scrapes by looking for "a*" or "sc" */
492 len = scan_urlencoded_query( &c, data = c, SCAN_PATH );
493
494 /* If parsing returned an error, leave with not found*/
495 if( len <= 0 ) HTTPERROR_404;
496
497 /* This is the hardcore match for announce*/
498 if( ( *data == 'a' ) || ( *data == '?' ) )
499 reply_size = http_handle_announce( client_socket, c );
500 else if( !byte_diff( data, 12, "scrape HTTP/" ) )
501 reply_size = http_handle_fullscrape( client_socket, recv_header, recv_length );
502 /* This is the hardcore match for scrape */
503 else if( !byte_diff( data, 2, "sc" ) )
504 reply_size = http_handle_scrape( client_socket, c );
505 /* All the rest is matched the standard way */
506 else switch( len ) {
507#ifdef WANT_TRACKER_SYNC
508 case 4: /* sync ? */
509 if( byte_diff( data, 4, "sync") ) HTTPERROR_404;
510 reply_size = http_handle_sync( client_socket, c );
511 break;
512#endif
513 case 5: /* stats ? */
514 if( byte_diff(data,5,"stats")) HTTPERROR_404;
515 reply_size = http_handle_stats( client_socket, c, recv_header, recv_length );
516 break;
517 default:
518 HTTPERROR_404;
519 }
520
521 /* If routines handled sending themselves, just return */
522 if( reply_size == -2 ) return 0;
523 /* If routine failed, let http error take over */
524 if( reply_size == -1 ) HTTPERROR_500;
525
526 /* This one is rather ugly, so I take you step by step through it.
527
528 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to
529 write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our static buffer, which is enough for the static string
530 plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate
531 the space NOT needed to expand in reply_off
532 */
533 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf( static_outbuf, 0, "%zd", reply_size );
534
535 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete
536 packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */
537 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 );
538
539 /* 3. Finally we join both blocks neatly */
540 static_outbuf[ SUCCESS_HTTP_HEADER_LENGTH - 1 ] = '\n';
541
542 http_senddata( client_socket, static_outbuf + reply_off, reply_size );
543 return reply_size;
544}