-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathechttp.c
1228 lines (1073 loc) · 41 KB
/
echttp.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* echttp - Embedded HTTP server.
*
* Copyright 2019, Pascal Martin
*
* 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.
*
* 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* ------------------------------------------------------------------------
* A minimal HTTP server library designed for simplicity and embedding in
* existing applications.
*
* void echttp_default (const char *arg);
*
* Set a default value for a command line option. The parameters must
* follow the exact same syntax as for command line options, i.e. it
* must be in the form "-option=value" or "-option". This function
* allows an application to override the echttp's own hardcoded
* option defaults and force its own.
*
* This can be called multiple times, and it must be before echttp_open().
*
* int echttp_open (int argc, const char **argv);
*
* Initialize the HTTP server. The HTTP-specific arguments are removed
* from the argument list and the count of remaining arguments is returned.
*
*
* typedef const char *echttp_callback (const char *method, const char *uri,
* const char *data, int length);
*
* int echttp_route_uri (const char *uri, echttp_callback *call);
*
* Define a route for processing the exact specified URI.
*
*
* typedef void echttp_protect_callback (const char *method, const char *uri);
*
* int echttp_protect (int route, echttp_protect_callback *call);
*
* Define a protect callback for the specified route.
*
*
* int echttp_route_match (const char *root, echttp_callback *call);
*
* Defines a route for a parent URI and all its children.
*
*
* const char *echttp_attribute_get (const char *name);
*
* Get the value of the specified HTTP attribute, or 0 if not found.
*
*
* const char *echttp_parameter_get (const char *name);
*
* Get the value of the specified HTTP parameter, or 0 if not found.
*
*
* void echttp_attribute_set (const char *name, const char *value);
*
* Set an attribute for the HTTP response.
*
*
* void echttp_error (int code, const char *message);
*
* Send an error response instead of OK.
*
*
* const char *echttp_reason (void);
*
* Return the current reason message for the current request. This function
* simply repeats the last message string that was set through echttp_error().
*
*
* void echttp_redirect (const char *url);
*
* Send a temporary redirect response instead of OK.
*
*
* int echttp_islocal (void);
*
* Return 1 if the current client is on a local network.
*
* int echttp_port (int ip);
*
* Return the web server's port number for IPv4 (ip==4) or IPv6 (ip==6).
* If the port number is 0, the web server is not listening on the specified
* IP namespace.
*
* int echttp_dynamic_port (void);
*
* Return true if the HTTP server uses a dynamic port, false otherwise.
* Dynamic ports are typically used when multiple HTTP servers run on
* the same machine (e.g. micro services), but require using a discovery
* service (e.g. houseportal). Dynamic port mode is activated using the
* command line option -http-service=dynamic.
*
* int echttp_connect (const char *host, const char *service);
*
* A simple helper for establishing a TCP connection. The returned socket
* has not been registered for listening: the application would have to
* call echttp_listen().
*
* typedef void *echttp_listener (int fd, int mode);
* void echttp_listen (int fd, int mode, echttp_listener *listener);
*
* Listen to the specified file descriptor (mode=0: don't listen,
* mode=1: read only, mode=2: write only, mode=3: read & write).
*
* When the specified file descriptor is ready, the listener is called
* with the mode corresponding to the event.
*
* void echttp_background (echttp_listener *listener);
*
* Call this listener completing I/O operations, before waiting for
* new I/O. This background listener is called with fd 0 and mode 0,
* and should not block on I/O itself.
*
* void echttp_loop (void);
*
* Enter the HTTP server main loop. The HTTP server may call any callback
* or listener fucntion, in any order.
*
*
* int echttp_isdebug (void);
*
* Return true if the HTTP debug option was set.
*
* echttp_close (void);
*
* Immediately close the HTTP server and all current HTTP connections.
*
* Web client functions:
*
* void echttp_escape (const char *s, char *d, int size);
*
* Encode an HTTP parameter value.
*
* const char *echttp_client (const char *method, const char *url);
*
* Establish a new web client context. Return a null pointer on success
* or an error string on failure. At this point the application may set
* attributes or set up a file for transfer before the last step: sending
* the request.
*
* typedef void echttp_response
* (void *origin, int status, char *data, int length);
*
* void echttp_submit (const char *data, int length,
* echttp_response *response, void *origin);
*
* Send the web request for the current web client context. This closes
* the web client context. When a response will be received from the
* server, the response function will be called, with the provided
* origin pointer as its first parameter.
*
* int echttp_redirected (const char *method);
*
* This helper function handles the common cases of redirections.
* If used, this must be called from the application's response callback,
* and it must be the first thing that the response callback do. How the
* response callback then behaves depends on the return code:
* >0: the response was not handled and the response must be processed
* as normal. The value returned is an updated HTTP status.
* 0: the response was handled and the response callback must now
* (re)build the request data, call echttp_submit and return.
* The method parameter is the method used for the original request.
* Depending on the redirect code, either this method will be used or
* else a GET method will be forced.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include "echttp.h"
#include "echttp_raw.h"
#include "echttp_tls.h"
#include "echttp_catalog.h"
#include "echttp_encoding.h"
static const char *echttp_service = "http";
static int echttp_debug = 0;
#define ECHTTP_STATE_IDLE 0
#define ECHTTP_STATE_CONTENT 1
#define ECHTTP_STATE_ERROR 2
#define ECHTTP_TRANSFER_IDLE 0
#define ECHTTP_TRANSFER_IN 1
#define ECHTTP_TRANSFER_OUT 2
enum echttp_transport {ECHTTP_RAW = 0, ECHTTP_TLS = 1};
typedef struct {
enum echttp_transport mode;
short state;
short protected;
int route;
int client;
int contentlength;
char method[64];
char uri[512];
char *content;
echttp_catalog in;
echttp_catalog out;
echttp_catalog params;
int status;
const char *reason;
struct {
int state; // IDLE, IN or OUT (see ECHTTP_TRANSFER_ constants)
int fd;
int size;
} transfer;
echttp_response *response;
echttp_response *asynchronous;
void *origin;
} echttp_request;
static echttp_request **echttp_context = 0;
static int echttp_context_count = 0;
static echttp_request *echttp_current = 0;
static int echttp_dynamic_flag = 0;
#define ECHTTP_MATCH_EXACT 1
#define ECHTTP_MATCH_PARENT 2
#define ECHTTP_MATCH_ANY (ECHTTP_MATCH_EXACT|ECHTTP_MATCH_PARENT)
typedef struct {
const char *uri;
echttp_callback *call;
echttp_callback *asynchronous;
echttp_protect_callback *protect;
unsigned int signature;
int match;
int next;
} echttp_route;
#define ECHTTP_MAX_ROUTES 512
static struct {
int count;
int index[ECHTTP_HASH];
echttp_route item[ECHTTP_MAX_ROUTES];
echttp_protect_callback *protect;
} echttp_routing;
static void echttp_transfer_reset (echttp_request *context) {
context->transfer.state = ECHTTP_TRANSFER_IDLE;
context->transfer.fd = -1;
context->transfer.size = 0;
}
static void echttp_transfer_cancel (echttp_request *context) {
if ((context->transfer.fd >= 0) &&
(context->transfer.state != ECHTTP_TRANSFER_IDLE)) {
close (context->transfer.fd);
}
echttp_transfer_reset (context);
}
static int echttp_split (char *data, const char *sep, char **items, int max) {
int count = 0;
int length = strlen(sep);
char *start = data;
while (*data) {
if (strncmp(sep, data, length) == 0) {
*data = 0;
if (count >= max) return count;
items[count++] = start;
data += length;
start = data;
} else {
data += 1;
}
}
if (data > start) items[count++] = start;
return count;
}
static void echttp_send (int client, const char *data, int length) {
switch (echttp_context[client]->mode) {
case ECHTTP_RAW:
echttp_raw_send (client, data, length);
break;
case ECHTTP_TLS:
echttp_tls_send (client, data, length);
break;
}
}
static void echttp_send_content (int client, const char *data, int length) {
echttp_request *context = echttp_context[client];
static const char eol[] = "\r\n";
int i;
char buffer[256];
int transfer_size = 0;
if ((context->transfer.size > 0) &&
(context->transfer.state == ECHTTP_TRANSFER_OUT))
transfer_size = context->transfer.size;
int size = snprintf (buffer, sizeof(buffer)-1,
"Content-Length: %d\r\n", length + transfer_size);
echttp_send (client, buffer, size);
for (i = 1; i <= context->out.count; ++i) {
if (context->out.item[i].name == 0) continue;
size = snprintf (buffer, sizeof(buffer)-1, "%s: %s\r\n",
context->out.item[i].name,
(char *)(context->out.item[i].value));
echttp_send (client, buffer, size);
}
echttp_send (client, eol, sizeof(eol)-1);
if (length > 0) {
echttp_send (client, data, length);
}
if (transfer_size > 0) {
// This transfer must be submitted to the raw layer only after
// all the preamble was submitted. Otherwise the raw layer may
// start the file transfer before the HTTP preamble was sent..
//
// Once this has been submitted to the raw layer, forget about it:
// the raw layer has become responsible for closing the file.
//
echttp_raw_transfer (client,
context->transfer.fd,
context->transfer.size);
echttp_transfer_reset (context); // Forget.
}
}
static void echttp_send_error (int client, int status, const char *text) {
static const char errorformat[] =
"HTTP/1.1 %d %s\r\n"
"Content-Length: 0\r\n"
"Connection: Closed\r\n\r\n";
echttp_request *context = echttp_context[client];
char buffer[1024];
int length = snprintf (buffer, sizeof(buffer), errorformat, status, text);
echttp_send (client, buffer, length);
echttp_transfer_cancel (context);
context->state = ECHTTP_STATE_ERROR;
}
static void echttp_unknown (int client) {
echttp_send_error (client, 404, "Not found");
}
static void echttp_invalid (int client, const char *text) {
echttp_send_error (client, 406, text);
}
static int echttp_has_error (int client) {
return ((echttp_context[client]->status / 100) > 3);
}
static int echttp_execute_protect (int route, int client,
const char *action, const char *uri) {
echttp_request *context = echttp_context[client];
if (context->protected) return 1; // No need to do this twice.
context->status = 200;
context->reason = "OK";
echttp_catalog_reset(&(context->out));
echttp_transfer_reset (context);
if (echttp_routing.protect) {
echttp_routing.protect (action, uri);
}
if (context->status == 200) {
if (echttp_routing.item[route].protect) {
echttp_routing.item[route].protect (action, uri);
}
}
if (context->status == 204) {
// 204 is not really an HTTP error, but at the protect phase
// this is a polite way to say "I will not process this".
//
echttp_send_error (client, 204, context->reason);
return 0; // Skip processing.
}
// Any other 2xx status is OK, but an HTTP error cancels the request.
//
if (echttp_has_error (client)) {
echttp_send_error (client, context->status, context->reason);
return 0; // Failed.
}
context->protected = 1;
return 1; // Passed.
}
static void echttp_execute_async (int route, int client,
const char *action, const char *uri,
const char *data, int length) {
echttp_request *context = echttp_context[client];
echttp_current = context;
if (! echttp_execute_protect (route, client, action, uri)) {
echttp_current = 0;
return;
}
echttp_routing.item[context->route].asynchronous (action, uri, data, length);
echttp_current = 0;
if ((context->status / 100) == 3) {
// This is a redirect and this request will not be processed further.
// The client will reissue a new request using the redirection URL and
// this connection will be closed. Better to send the HTTP status now
// and ignore any of the content data that will not be used anyway.
//
char buffer[256];
snprintf (buffer, sizeof(buffer), "HTTP/1.1 %d %s\r\n",
context->status, context->reason);
echttp_send (client, buffer, strlen(buffer));
echttp_send_content (client, 0, 0);
context->state = ECHTTP_STATE_ERROR; // Ignore further data.
}
if (echttp_has_error (client)) {
// This is bad, as the request is nowhere complete.
// Send a status now and cancel the whole request.
echttp_send_error (client, context->status, context->reason);
}
// Do not send anything when there is no error or redirection, since
// we are still waiting for more content data.
}
static void echttp_execute (int route, int client,
const char *action, const char *uri,
const char *data, int length) {
echttp_request *context = echttp_context[client];
// Do not rely on echttp_current internally: the application is allowed
// to submit a client request in reaction to the received request,
// so the current context might have been replaced.
//
const char *connection = echttp_catalog_get (&(context->in), "Connection");
int keep = (connection && (strcmp(connection, "keep-alive") == 0));
echttp_current = context;
if (! echttp_execute_protect (route, client, action, uri)) {
echttp_current = 0;
return;
}
data = echttp_routing.item[route].call (action, uri, data, length);
echttp_current = 0;
if (echttp_has_error (client)) {
echttp_send_error (client, context->status, context->reason);
return;
}
length = data?strlen(data):0;
char buffer[256];
snprintf (buffer, sizeof(buffer), "HTTP/1.1 %d %s\r\n",
context->status, context->reason);
echttp_send (client, buffer, strlen(buffer));
if (keep) {
static const char text[] = "Connection: keep-alive\r\n";
echttp_send (client, text, sizeof(text)-1);
}
echttp_send_content (client, data, length);
}
static int echttp_route_add (const char *uri, echttp_callback *call, int match) {
int i = echttp_routing.count + 1;
unsigned int signature = echttp_hash_signature (uri);
int index = signature % ECHTTP_HASH;
if (i >= ECHTTP_MAX_ROUTES) {
fprintf (stderr, "Too many routes.\n");
return -1;
}
echttp_routing.item[i].uri = uri;
echttp_routing.item[i].call = call;
echttp_routing.item[i].protect = 0;
echttp_routing.item[i].asynchronous = 0;
echttp_routing.item[i].match = match;
echttp_routing.item[i].signature = signature;
echttp_routing.item[i].next = echttp_routing.index[index];
echttp_routing.index[index] = i;
echttp_routing.count = i;
return i;
}
static int echttp_route_search (const char *uri, int match) {
int i;
unsigned int signature = echttp_hash_signature (uri);
int index = signature % ECHTTP_HASH;
static char *toascii[] = {"(invalid)", "exact", "parent", "any"};
if (echttp_debug)
printf ("Searching route for %s (match %s)\n", uri, toascii[match]);
for (i = echttp_routing.index[index];
i > 0; i = echttp_routing.item[i].next) {
if ((echttp_routing.item[i].match & match) == 0) continue;
if (echttp_debug)
printf ("Matching with %s (%s entry)\n",
echttp_routing.item[i].uri,
toascii[echttp_routing.item[i].match]);
if ((echttp_routing.item[i].signature == signature) &&
strcmp (echttp_routing.item[i].uri, uri) == 0) return i;
}
return -1;
}
int echttp_route_find (const char *uri) {
return echttp_route_search (uri, ECHTTP_MATCH_ANY);
}
static void echttp_respond_async (int client, char *data, int length) {
// Do not rely on echttp_current internally: this is processing a response
// and there is no current request processing.
//
echttp_request *context = echttp_context[client];
if (context->asynchronous) {
echttp_current = context;
context->asynchronous (context->origin, context->status, data, length);
echttp_current = 0;
context->asynchronous = 0;
}
}
static void echttp_respond (int client, char *data, int length) {
// Do not rely on echttp_current internally: this is processing a response
// and there is no current request processing.
//
echttp_request *context = echttp_context[client];
if (context->response) {
echttp_current = context;
context->response (context->origin, context->status, data, length);
echttp_current = 0;
context->response = 0;
}
context->origin = 0;
echttp_catalog_reset(&(context->in));
}
static int echttp_newclient (int client) {
if (client >= echttp_context_count) {
fprintf (stderr, "Invalid client context\n");
return -1;
}
if (echttp_debug) printf ("New client %d is reported\n", client);
echttp_request *context = echttp_context[client];
if (!context) {
context = echttp_context[client] = malloc (sizeof(echttp_request));
context->client = client;
}
context->state = ECHTTP_STATE_IDLE;
context->mode = ECHTTP_RAW;
echttp_transfer_reset (context);
context->response = 0;
context->asynchronous = 0;
context->origin = 0;
context->route = 0;
echttp_catalog_reset(&(context->in));
echttp_catalog_reset(&(context->out));
return 1;
}
static int echttp_received (int client, char *data, int length) {
static const char endpattern[] = "\r\n\r\n";
int i;
char *endreq;
char *enddata = data + length;
int consumed = 0;
echttp_request *context = echttp_context[client];
if (length < 0) {
// This reports a TCP connection error.
if (echttp_debug)
printf ("End of connection while waiting for %s\n",
context->response?"response":"request");
echttp_transfer_cancel(context); // Done with data transfer, if any.
if (context->response) {
context->status = 505;
echttp_respond (client, 0, 0);
}
return 0; // The connection will be closed by echttp_raw.
}
// If any error has been detected, ignore all further data until the
// connection is closed.
if (context->state == ECHTTP_STATE_ERROR) return length;
if (echttp_debug)
printf ("Received HTTP %s (%d bytes)\n",
echttp_context[client]->response?"response":"request", length);
data[length] = 0;
// If there was content left to receive, accumulate it in the echttp_raw
// buffer (synchronous mode), or write it to the file provided (asynchronous
// mode).
// When we received it all, execute the HTTP request. Otherwise wait for more.
//
if (context->state == ECHTTP_STATE_CONTENT) {
if (context->transfer.state == ECHTTP_TRANSFER_IN) { // Asynchronous.
int size = length;
if (context->transfer.size < length) size = context->transfer.size;
size = write (context->transfer.fd, data, size);
if (size <= 0) {
context->state = ECHTTP_STATE_ERROR;
echttp_transfer_cancel(context);
return length; // Discard all received data.
}
context->transfer.size -= size;
if (context->transfer.size <= 0) {
echttp_transfer_cancel(context); // Done with that transfer.
if (context->response) {
// Client connections are not reused: handle the response
// and close.
echttp_respond (client, 0, 0);
echttp_raw_close_client(context->client, "end of response");
return 0; // Connection was closed, nothing more to do.
}
// Server connections are kept open: the client may submit
// a subsequent request.
context->state = ECHTTP_STATE_IDLE;
echttp_execute (context->route, client,
context->method, context->uri, 0, 0);
}
return size;
}
// Synchronous.
if (context->contentlength > length) return 0; // Wait for more.
if (context->response) {
// Client connections are not reused: handle the response
// and close.
echttp_respond (client, data, context->contentlength);
echttp_raw_close_client(context->client, "end of response");
return 0; // Connection was closed, nothing more to do.
}
// Server connections are kept open: the client may submit
// a subsequent request.
echttp_execute (context->route, client,
context->method, context->uri,
data, context->contentlength);
consumed = context->contentlength;
data += context->contentlength;
context->state = ECHTTP_STATE_IDLE;
}
// We are waiting for a new HTTP PDU.
// The HTTP standard allows sending requests back to back, however
// echttp cannot send a new response before the previous has been sent
// when the transfer mechanism is being used. The problem is that there
// is no mechanism to sequence multiple transfers one after the other,
// combined with buffer data.
// Here we loop _until_ a full HTTP request has been processed, at which
// time the code breaks free.
//
while ((data < enddata) && ((endreq = strstr(data, endpattern)) != 0)) {
// Decode this complete HTTP header.
char *line[256];
*endreq = 0;
endreq += strlen(endpattern);;
int linecount = echttp_split (data, "\r\n", line, 256);
if (context->response) {
// Expect a status line.
if (echttp_debug) printf("HTTP status: %s\n", line[0]);
if (strncmp (line[0], "HTTP/1.", 7)) {
context->status = 505;
echttp_respond (client, 0, 0);
echttp_raw_close_client(context->client, "protocol error");
return 0; // Connection was closed, nothing more to do.
}
context->status = atoi(strchr(line[0], ' '));
if (context->status < 100 || context->status >= 600) context->status = 500;
} else {
// Expect a request line.
if (echttp_debug) printf("HTTP request: %s\n", line[0]);
char *request[4];
char *rawuri[4];
int wordcount = echttp_split (line[0], " ", request, 4);
if (wordcount != 3) {
echttp_invalid (client, "Invalid Request Line");
return length; // Consume everything, since we are closing.
}
wordcount = echttp_split (request[1], "?", rawuri, 4);
char *method = echttp_encoding_unescape(request[0]);
char *uri = echttp_encoding_unescape(rawuri[0]);
if (strstr (uri, "..")) {
// There is no legitimate reason to use ".." in any URL, even
// this is not a file URL.
// (The same protection is already implemented in echttp_static
// and then in a few other places. That it was needed in more
// than one place is what motivated this low-level check: too
// much risk of having the check missing in some places.
echttp_raw_close_client(context->client, "path traversal");
return 0; // Connection was closed, nothing more to do.
}
if ((method == 0) || (uri == 0)) {
echttp_invalid (client, "Invalid request format");
return length; // Consume everything, since we are closing.
}
snprintf (context->method, sizeof(context->method), method);
snprintf (context->uri, sizeof(context->uri), uri);
echttp_catalog_reset(&(context->params));
if (wordcount == 2) {
char *arg[32];
wordcount = echttp_split (rawuri[1], "&", arg, 32);
for (i = 0; i < wordcount; ++i) {
char *param[4];
if (echttp_split (arg[i], "=", param, 4) >= 2) {
char *name = echttp_encoding_unescape (param[0]);
char *value = echttp_encoding_unescape (param[1]);
if (!name || !value) {
echttp_invalid (client, "Invalid Parameter Syntax");
return length; // Consume everything, invalid.
}
echttp_catalog_set (&(context->params), name, value);
}
}
}
// Search for a uri mapping: try any match first, then for a parent.
//
i = echttp_route_search (context->uri, ECHTTP_MATCH_ANY);
if (i <= 0) {
char *uri = strdup(context->uri);
char *sep = strrchr (uri+1, '/');
while (sep) {
*sep = 0;
i = echttp_route_search (uri, ECHTTP_MATCH_PARENT);
if (i > 0) break;
sep = strrchr (uri+1, '/');
}
if (i <= 0) {
i = echttp_route_search ("/", ECHTTP_MATCH_PARENT);
}
free(uri);
if (i <= 0) {
echttp_unknown (client);
return length; // Consume everything, since we are closing.
}
}
context->route = i;
}
// Decode the header attributes after the request/status line.
//
echttp_catalog_reset(&(context->in));
for (i = 1; i < linecount; ++i) {
char *param[4];
if (echttp_split (line[i], ": ", param, 4) >= 2) {
echttp_catalog_set (&(context->in), param[0], param[1]);
}
}
// At this point all the HTTP request and header lines were decoded
// and we are ready to act on that new request.
//
context->protected = 0; // The protect callbacks were not called yet.
// Retrieve the content data already received.
//
const char *field = echttp_catalog_get(&(context->in), "Content-Length");
context->contentlength = 0;
context->content = endreq;
if (field) {
int available = (int)(enddata - endreq);
context->contentlength = atoi(field);
if (context->contentlength < 0) context->contentlength = 0;
if (context->contentlength > available) {
if (echttp_debug) printf("HTTP: waiting for end of content.\n");
context->state = ECHTTP_STATE_CONTENT;
consumed += ((int) (endreq - data)); // Consumed the header.
// Asynchronous request (client side).
if (context->asynchronous) {
if (echttp_debug) printf("HTTP: asynchronous response.\n");
echttp_respond_async (client, context->content, available);
if (context->state == ECHTTP_STATE_ERROR) return length;
if (context->transfer.state == ECHTTP_TRANSFER_IN)
consumed += available; // Consumed the received content.
// Asynchronous endpoint (server side).
} else if ((context->route > 0) &&
echttp_routing.item[context->route].asynchronous) {
if (echttp_debug) printf("HTTP: asynchronous request.\n");
echttp_execute_async (context->route, client,
context->method, context->uri,
context->content, available);
if (context->state == ECHTTP_STATE_ERROR) return length;
if (context->transfer.state == ECHTTP_TRANSFER_IN)
consumed += available; // Consumed the received content.
}
return consumed; // Wait for more.
}
}
if (!context->contentlength) context->content = 0;
consumed += ((int) (endreq - data) + context->contentlength);
data = endreq + context->contentlength;
if (context->response) {
echttp_respond (client, context->content, context->contentlength);
echttp_raw_close_client(context->client, "end of response");
return 0; // Nothing more to do with this connection.
}
echttp_execute (context->route, client,
context->method, context->uri,
context->content, context->contentlength);
// Avoid processing a subsequent request before the response has been
// sent. Otherwise there could be a problem with concurrent transfers.
break;
}
return consumed;
}
static void echttp_terminate (int client, const char *reason) {
if (!echttp_context[client]) return;
switch (echttp_context[client]->mode) {
case ECHTTP_RAW:
break;
case ECHTTP_TLS:
echttp_tls_detach_client (client, reason);
break;
}
}
const char *echttp_help (int level) {
static const char *httpHelp[] = {
" [-http-service=NAME] [-http-debug]",
"-http-service=NAME: name or port number for the HTTP socket (http).",
"-http-debug: enable debug traces.",
NULL
};
return httpHelp[level];
}
void echttp_default (const char *arg) {
if (echttp_option_match ("-http-service=", arg, &echttp_service)) return;
if (echttp_option_present ("-http-debug", arg)) {
echttp_debug = 1;
return;
}
}
int echttp_open (int argc, const char **argv) {
int i;
int shift;
int ttl = 0;
const char *ttl_ascii;
for (i = 1, shift = 1; i < argc; ++i) {
if (shift != i) argv[shift] = argv[i];
if (echttp_option_match ("-http-service=", argv[i], &echttp_service))
continue;
if (echttp_option_match ("-http-ttl=", argv[i], &ttl_ascii)) {
ttl = atoi(ttl_ascii);
if (ttl < 0) ttl = 0;
continue;
}
if (echttp_option_present ("-http-debug", argv[i])) {
echttp_debug = 1;
continue;
}
shift += 1;
}
echttp_routing.count = 0;
echttp_routing.protect = 0;
if (! echttp_raw_open (echttp_service, echttp_debug, ttl)) return -1;
echttp_context_count = echttp_raw_capacity();
echttp_context = calloc (echttp_context_count, sizeof(echttp_request *));
echttp_dynamic_flag = (strcmp(echttp_service, "dynamic") == 0);
return echttp_tls_initialize (echttp_context_count, shift, argv);
}
void echttp_loop (void) {
echttp_raw_loop (echttp_newclient, echttp_received, echttp_terminate);
echttp_raw_close ();
}
void echttp_close (void) {
echttp_raw_close ();
}
int echttp_route_uri (const char *uri, echttp_callback *call) {
return echttp_route_add (uri, call, ECHTTP_MATCH_EXACT);
}
int echttp_route_match (const char *root, echttp_callback *call) {
return echttp_route_add (root, call, ECHTTP_MATCH_PARENT);
}
int echttp_protect (int route, echttp_protect_callback *call) {
if (route < 0 || route > echttp_routing.count) return -1;
if (route == 0)
echttp_routing.protect = call;
else
echttp_routing.item[route].protect = call;
return route;
}
int echttp_asynchronous_route (int route, echttp_callback *callback) {
if (route <= 0 || route > echttp_routing.count) return -1;
echttp_routing.item[route].asynchronous = callback;
return route;
}
const char *echttp_attribute_get (const char *name) {
if (! echttp_current) return 0;
return echttp_catalog_get (&(echttp_current->in), name);
}
const char *echttp_parameter_get (const char *name) {
if (! echttp_current) return 0;
return echttp_catalog_get (&(echttp_current->params), name);
}
void echttp_parameter_join (char *text, int size) {
if (! echttp_current) return;
echttp_catalog_join (&(echttp_current->params), "&", text, size);
}
void echttp_attribute_set (const char *name, const char *value) {
if (! echttp_current) return;
echttp_catalog_set (&(echttp_current->out), name, value);
}
void echttp_content_type_set (const char *value) {
if (! echttp_current) return;
echttp_catalog_set (&(echttp_current->out), "Content-Type", value);
}
void echttp_content_type_json (void) {
if (! echttp_current) return;
echttp_catalog_set (&(echttp_current->out),
"Content-Type", "application/json");
}
void echttp_content_type_html (void) {
if (! echttp_current) return;
echttp_catalog_set (&(echttp_current->out), "Content-Type", "text/html");
}
void echttp_content_type_css (void) {
if (! echttp_current) return;
echttp_catalog_set (&(echttp_current->out), "Content-Type", "text/css");
}
void echttp_transfer (int fd, int size) {
if (!echttp_current) return;
if (echttp_current->transfer.state != ECHTTP_TRANSFER_IDLE) return;