-
Notifications
You must be signed in to change notification settings - Fork 13
/
EmbeddableWebServer.h
executable file
·2418 lines (2246 loc) · 114 KB
/
EmbeddableWebServer.h
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
/* EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and CONTRIBUTORS (see the end of this file) - All rights reserved.
Released under the BSD 2-clause license:
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
// The latest version is probably at https://www.forrestheller.com/embeddable-c-web-server/
/* This is a very simple web server that you can embed in your application to both handle requests
dynamically and serve files. The idea is that it has no dependencies and is really easy to drop into a project.
Here's the simplest way to get started:
1. Call acceptConnectionsUntilStoppedFromEverywhereIPv4(NULL), which will initialize a new server and block
Note: If you just want to take connections from a specific inteface/localhost you can use acceptConnectionsUntilStopped
2. Fill out createResponseForRequest. Use the responseAlloc* functions to return a response or take over the connection
yourself and return NULL. The easiest way to serve static files is responseAllocServeFileFromRequestPath. The easiest
way to serve HTML is responseAllocHTML. The easiest way to serve JSON is responseAllocJSON. The server will free() your
response once it's been sent. See the README for a quick example and the EWSDemo.cpp file for more examples such as file
serving, HTML form processing, and JSON.
EWS runs on Windows, Linux, and Mac OS X. It currently requires dynamic memory especially when dealing with strings.
It is *not suitable for Internet serving* because it has not been thoroughly designed+tested for security.
It uses a thread per connection model, where each HTTP connection is handled by a newly spawned thread. This lets
certain requests take a long time to handle while other requests can still quickly be handled.
Tips:
* Use the heapStringAppend*(&response->body) functions to dynamically build a body (see the HTML form POST demo)
* For debugging use connectionDebugStringCreate
* Gain extra debugging by enabling ews_print_debug
* If you want a clean server shutdown you can use serverInit() + acceptConnectionsUntilStopped() + serverDeInit()
* To include the file in multiple .c/.cpp files use EWS_HEADER_ONLY in all places but one. This is the opposite of
STB_IMPLEMENTATION if you are familiar with the STB libraries
* To run a server on a different thread use (even on Windows):
#include "EmbeddableWebServer.h"
#include <time.h>
#ifdef WIN32
#pragma comment(lib, "ws2_32") // link against Winsock on Windows
#endif
static struct Server server;
static THREAD_RETURN_TYPE STDCALL_ON_WIN32 acceptConnectionsThread(void* unusedParam) {
serverInit(&server);
const uint16_t portInHostOrder = 8080;
acceptConnectionsUntilStoppedFromEverywhereIPv4(&server, portInHostOrder);
return (THREAD_RETURN_TYPE) 0;
}
struct Response* createResponseForRequest(const struct Request* request, struct Connection* connection) {
time_t t;
time(&t);
return responseAllocHTMLWithFormat("<html><h1>The time is seconds is %ld</h1></html>", t);
}
int main() {
pthread_t threadHandle;
pthread_create(&threadHandle, NULL, &acceptConnectionsThread, NULL);
// rest of the program
while (1) {
}
return 0;
}
*/
/* You can turn these prints on/off. ews_printf generally prints warnings + errors while ews_print_debug prints mundane information */
#define ews_printf printf
//#define ews_printf(...)
//#define ews_printf_debug printf
#define ews_printf_debug(...)
#include <stdbool.h>
/* Quick nifty options */
static bool OptionPrintWholeRequest = false;
/* /status page - makes quite a few things take a lock to update counters but it doesn't make much of a difference. This isn't something like Nginx or Haywire*/
static bool OptionIncludeStatusPageAndCounters = true;
/* If using responseAllocServeFileFromRequestPath and no index.html is found, serve up the directory */
static bool OptionListDirectoryContents = true;
/* Print the entire server response to every request */
static bool OptionPrintResponse = false;
/* These bound the memory used by a request. The headers used to be dynamically allocated but I've made them hard coded because: 1. Memory used by a request should be bounded 2. It was responsible for 2 * headersCount allocations every request */
#define REQUEST_MAX_HEADERS 64
#define REQUEST_HEADERS_MAX_MEMORY (8 * 1024)
#define REQUEST_MAX_BODY_LENGTH (128 * 1024 * 1024) /* (rather arbitrary) */
/* the buffer in connection used for sending and receiving. Should be big enough to fread(buffer) -> send(buffer) */
#define SEND_RECV_BUFFER_SIZE (16 * 1024)
/* contains the Response HTTP status and headers */
#define RESPONSE_HEADER_SIZE 1024
#define EMBEDDABLE_WEB_SERVER_VERSION_STRING "1.1.3"
#define EMBEDDABLE_WEB_SERVER_VERSION 0x00010103 // major = [31:16] minor = [15:8] build = [7:0]
/* has someone already enabled _CRT_SECURE_NO_WARNINGS? If so, don't enable it again. If not, disable it for us. */
#ifdef _CRT_SECURE_NO_WARNINGS
#define UNDEFINE_CRT_SECURE_NO_WARNINGS 0
#else
#define UNDEFINE_CRT_SECURE_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1
#endif //_CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <stdint.h>
#include <inttypes.h>
#ifdef WIN32
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <Windows.h>
typedef int64_t ssize_t;
/* If mingw/msys2 pthreads library is in use, it defines WIN_PHTREADS_H. Thus we do not need to include our own mini-pthreads */
#ifndef WIN_PTHREADS_H
typedef HANDLE pthread_t;
typedef CRITICAL_SECTION pthread_mutex_t;
typedef CONDITION_VARIABLE pthread_cond_t;
#define THREAD_RETURN_TYPE DWORD
#else
#define THREAD_RETURN_TYPE void*
#endif // ! defined WIN_PTHREADS_H
typedef SOCKET sockettype;
#define STDCALL_ON_WIN32 WINAPI
#else // not WIN32 - macOS/Linux
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pthread.h>
#include <ifaddrs.h>
#include <sys/stat.h>
#include <dirent.h>
#include <strings.h>
typedef int sockettype;
#define STDCALL_ON_WIN32
#define THREAD_RETURN_TYPE void*
#endif
typedef enum {
RequestParseStateMethod,
RequestParseStatePath,
RequestParseStateVersion,
RequestParseStateHeaderName,
RequestParseStateHeaderValue,
RequestParseStateCR,
RequestParseStateCRLF,
RequestParseStateCRLFCR,
RequestParseStateBody,
RequestParseStateEatHeaders,
RequestParseStateDone
} RequestParseState;
/* just a calloc'd C string on the heap */
struct HeapString {
char* contents; // null-terminated, at least length+1
size_t length; // this is updated by the heapString* functions
size_t capacity;
};
/* a string pointing to the request->headerStringPool */
struct PoolString {
char* contents; // null-terminated
size_t length;
};
struct Header {
struct PoolString name;
struct PoolString value;
};
/* You'll look directly at this struct to handle HTTP requests. It's initialized
by setting everything to 0 */
struct Request {
/* null-terminated HTTP method (GET, POST, PUT, ...) */
char method[64];
size_t methodLength;
/* null-terminated HTTP version string (HTTP/1.0) */
char version[16];
size_t versionLength;
/* null-terminated HTTP path/URI ( /index.html?name=Forrest%20Heller ) */
char path[1024];
size_t pathLength;
/* null-terminated HTTP path/URI that has been %-unescaped. Used for a file serving.
path=/index.html?%20var=s%20p pathDecoded=/index.html? var=s p */
char pathDecoded[1024];
size_t pathDecodedLength;
/* null-terminated string containing the request body. Used for POST forms and JSON blobs */
struct HeapString body;
/* HTTP request headers - use headerInRequest to find the header you're looking for. These used to be a linked list and that worked well, but it seemed overkill */
struct Header headers[REQUEST_MAX_HEADERS];
size_t headersCount;
/* the this->headers point at this string pool */
char headersStringPool[REQUEST_HEADERS_MAX_MEMORY];
size_t headersStringPoolOffset;
/* Since this has many fixed fields, we report when we went over the limit */
struct Warnings {
/* Was some header information discarded because there was not enough room in the pool? */
bool headersStringPoolExhausted;
/* Were there simply too many headers in this request for us to handle them all? */
bool tooManyHeaders;
/* request line strings truncated? */
bool methodTruncated;
bool versionTruncated;
bool pathTruncated;
bool bodyTruncated;
} warnings;
/* internal state for the request parser */
RequestParseState state;
};
struct ConnectionStatus {
int64_t bytesSent;
int64_t bytesReceived;
};
/* This contains a full HTTP connection. For every connection, a thread is spawned
and passed this struct */
struct Connection {
/* Just allocate the buffers in the connection. These go at the beginning of
the connection in the hopes that they are 'more aligned' */
char sendRecvBuffer[SEND_RECV_BUFFER_SIZE];
char responseHeader[RESPONSE_HEADER_SIZE];
sockettype socketfd;
/* Who connected? */
struct sockaddr_storage remoteAddr;
socklen_t remoteAddrLength;
char remoteHost[128];
char remotePort[16];
struct ConnectionStatus status;
struct Request request;
/* points back to the server, usually used for the server's globalMutex */
struct Server* server;
};
/* You create one of these for the server to send. Use one of the responseAlloc functions.
You can fill out the body field using the heapString* functions. You can also specify a
filenameToSend which will be sent using regular file streaming. This is so you don't have
to load the entire file into memory all at once to send it. */
struct Response {
int code;
struct HeapString body;
char* filenameToSend;
char* status;
char* contentType;
char* extraHeaders; // can be NULL
};
struct Server {
bool initialized;
pthread_mutex_t globalMutex;
bool shouldRun;
sockettype listenerfd;
/* User field for whatever - if your request handler you can do connection->server->tag */
void* tag;
/* The rest of the vars just have to do with shutting down the server cleanly.
It's a lot of work, actually! Much simpler when I just let it run forever */
bool stopped;
pthread_mutex_t stoppedMutex;
pthread_cond_t stoppedCond;
int activeConnectionCount;
pthread_cond_t connectionFinishedCond;
pthread_mutex_t connectionFinishedLock;
};
#ifndef __printflike
#define __printflike(...) // clang (and maybe GCC) has this macro that can check printf/scanf format arguments
#endif
/* You fill in this function. Look at request->path for the requested URI */
struct Response* createResponseForRequest(const struct Request* request, struct Connection* connection);
/* To embed just call one of these functions. They will accept connections until you call serverStop on the server.
You can also just pass NULL for server if you just want the server to run forever */
int acceptConnectionsUntilStoppedFromEverywhereIPv4(struct Server* serverOrNULL, uint16_t portInHostOrder);
int acceptConnectionsUntilStopped(struct Server* serverOrNULL, const struct sockaddr* address, socklen_t addressLength);
/* use these in createResponseForRequest */
/* Allocate a response with an initial body size that you can strcpy to */
struct Response* responseAlloc(int code, const char* status, const char* contentType, size_t contentsLength);
/* Serve a file from documentRoot. If you just want to serve the current directory over HTTP just do "."
To serve out the current directory like a normal web server do:
responseAllocServeFileFromRequestPath("/", request->path, request->pathDecoded, ".")
To serve files with a prefix do this:
responseAllocServeFileFromRequestPath("/release/current", request->path, request->pathDecoded, "/var/root/www/release-5.0.0") so people will go to:
http://55.55.55.55/release/current and be served /var/root/www/release-5.0.0 */
struct Response* responseAllocServeFileFromRequestPath(const char* pathPrefix, const char* requestPath, const char* requestPathDecoded, const char* documentRoot);
/* You can use heapStringAppend*(&response->body) to dynamically generate the body */
struct Response* responseAllocHTML(const char* html);
struct Response* responseAllocHTMLWithFormat(const char* format, ...) __printflike(1, 0);
struct Response* responseAllocHTMLWithStatus(int code, const char* status, const char* html);
struct Response* responseAllocJSON(const char* json);
struct Response* responseAllocJSONWithStatus(int code, const char* status, const char* json);
struct Response* responseAllocJSONWithFormat(const char* format, ...) __printflike(1, 0);
struct Response* responseAllocWithFormat(int code, const char* status, const char* contentType, const char* format, ...) __printflike(3, 0);
/* If you leave the MIMETypeOrNULL NULL, the MIME type will be auto-detected */
struct Response* responseAllocWithFile(const char* filename, const char* MIMETypeOrNULL);
/* Error messages for when the request can't be handled properly */
struct Response* responseAlloc400BadRequestHTML(const char* errorMessage);
struct Response* responseAlloc404NotFoundHTML(const char* resourcePathOrNull);
struct Response* responseAlloc500InternalErrorHTML(const char* extraInformationOrNull);
/* If you care about initialization and tear-down or managing multiple servers
you'll want to use these functions. Otherwise you can just pass null to acceptConnections* */
void serverInit(struct Server* server);
void serverDeInit(struct Server* server);
void serverStop(struct Server* server);
/* Wrappers around strdupDecodeGetorPOSTParam */
char* strdupDecodeGETParam(const char* paramNameIncludingEquals, const struct Request* request, const char* valueIfNotFound);
char* strdupDecodePOSTParam(const char* paramNameIncludingEquals, const struct Request* request, const char* valueIfNotFound);
/* You can pass this the request->path for GET or request->body.contents for POST. Accepts NULL for paramString for convenience */
char* strdupDecodeGETorPOSTParam(const char* paramNameIncludingEquals, const char* paramString, const char* valueIfNotFound);
/* If you want to echo back HTML into the value="" attribute or display some user output this will help you (like > <) */
char* strdupEscapeForHTML(const char* stringToEscape);
/* If you have a file you reading/writing across connections you can use this provided pthread mutex so you don't have to make your own */
/* Need to inspect a header in a request? */
const struct Header* headerInRequest(const char* headerName, const struct Request* request);
/* Get a debug string representing this connection that's easy to print out. wrap it in HTML <pre> tags */
struct HeapString connectionDebugStringCreate(const struct Connection* connection);
/* Some really basic dynamic string handling. AppendChar and AppendFormat allocate enough memory and
these strings are null-terminated so you can pass them into regular string functions. */
void heapStringInit(struct HeapString* string);
void heapStringFreeContents(struct HeapString* string);
void heapStringSetToCString(struct HeapString* heapString, const char* cString);
void heapStringAppendChar(struct HeapString* string, char c);
void heapStringAppendFormat(struct HeapString* string, const char* format, ...) __printflike(2, 0);
void heapStringAppendString(struct HeapString* string, const char* stringToAppend);
void heapStringAppendFormatV(struct HeapString* string, const char* format, va_list ap);
void heapStringAppendHeapString(struct HeapString* target, const struct HeapString* source);
/* functions that help when serving files */
const char* MIMETypeFromFile(const char* filename, const uint8_t* contents, size_t contentsLength);
/* These are handy if you need to do something like serialize access to a file */
int serverMutexLock(struct Server* server);
int serverMutexUnlock(struct Server* server);
/* runs quick unit tests in the demo app */
void EWSUnitTestsRun(void);
#ifndef EWS_HEADER_ONLY
/* Internal implementation stuff */
/* these counters exist solely for the purpose of the /status demo */
static struct Counters {
bool lockInitialized;
pthread_mutex_t lock;
int64_t bytesReceived;
int64_t bytesSent;
int64_t totalConnections;
int64_t activeConnections;
int64_t heapStringAllocations;
int64_t heapStringReallocations;
int64_t heapStringFrees;
int64_t heapStringTotalBytesReallocated;
} counters;
#ifndef MIN
#define MIN(a, b) ((a < b) ? a : b)
#endif
struct PathInformation {
bool exists;
bool isDirectory;
};
static void responseFree(struct Response* response);
static void printIPv4Addresses(uint16_t portInHostOrder);
static struct Connection* connectionAlloc(struct Server* server);
static void connectionFree(struct Connection* connection);
static void requestParse(struct Request* request, const char* requestFragment, size_t requestFragmentLength);
static int acceptConnectionsUntilStoppedInternal(struct Server* server, const struct sockaddr* address, socklen_t addressLength);
static size_t heapStringNextAllocationSize(size_t required);
static void poolStringStartNewString(struct PoolString* poolString, struct Request* request);
static void poolStringAppendChar(struct Request* request, struct PoolString* string, char c);
static bool strEndsWith(const char* big, const char* endsWith);
static void ignoreSIGPIPE(void);
static void callWSAStartupIfNecessary(void);
static FILE* fopen_utf8_path(const char* utf8Path, const char* mode);
static int pathInformationGet(const char* path, struct PathInformation* info);
static int sendResponseBody(struct Connection* connection, const struct Response* response, ssize_t* bytesSent);
static int sendResponseFile(struct Connection* connection, const struct Response* response, ssize_t* bytesSent);
static int snprintfResponseHeader(char* destination, size_t destinationCapacity, int code, const char* status, const char* contentType, const char* extraHeaders, size_t contentLength);
#ifdef WIN32 /* Windows implementations of functions available on Linux/Mac OS X */
/* opendir/readdir/closedir API implementation with FindNextFile */
struct dirent {
char d_name[1024];
};
typedef struct DIRStruct {
HANDLE findFiles;
WIN32_FIND_DATAW findData;
bool onFirstFile;
struct dirent currentEntry;
} DIR;
static DIR* opendir(const char* path);
static struct dirent* readdir(DIR* dirHandle);
static int closedir(DIR* dirHandle);
#ifndef WIN_PTHREADS_H
/* pthread implementation with critical sections and conditions */
static int pthread_detach(pthread_t threadHandle);
static int pthread_create(HANDLE* threadHandle, const void* attributes, LPTHREAD_START_ROUTINE thread, void* param);
static int pthread_cond_init(pthread_cond_t* cond, const void* attributes);
static int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
static int pthread_cond_signal(pthread_cond_t* cond);
static int pthread_cond_destroy(pthread_cond_t* cond);
static int pthread_mutex_init(pthread_mutex_t* mutex, const void* attributes);
static int pthread_mutex_lock(pthread_mutex_t* mutex);
static int pthread_mutex_unlock(pthread_mutex_t* mutex);
static int pthread_mutex_destroy(pthread_mutex_t* mutex);
#endif // ! defined WIN_PTHREADS_H
/* It was pointed out to me that snprintf is implemented in VS2015 and later*/
#if defined(_MSC_VER) && _MSC_VER < 1900 /* 1900 = VS2015 */
#define EWS_IMPLEMENT_SPRINTF 1
#else
#define EWS_IMPLEMENT_SPRINTF 0
#endif
#if EWS_IMPLEMENT_SPRINTF
static int snprintf(char* destination, size_t length, const char* format, ...);
#endif
static wchar_t* strdupWideFromUTF8(const char* utf8String, size_t extraBytes);
/* windows function aliases */
#ifndef strcasecmp
#define strcasecmp _stricmp
#endif // defined strcasecmp
#define strdup(string) _strdup(string)
#define unlink(file) _unlink(file)
#define close(x) closesocket(x)
#define gai_strerror_ansi(x) gai_strerrorA(x)
#else // WIN32
#define gai_strerror_ansi(x) gai_strerror(x)
#define EWS_IMPLEMENT_SPRINTF 0
#endif // Linux/Mac OS X
#ifdef EWS_FUZZ_TEST
#define recv(socket, buffer, bufferLength, flags) read(socket, buffer, bufferLength)
#define send(socket, buffer, bufferLength, flags) write(STDOUT_FILENO, buffer, bufferLength)
#define CHECK_SERVED_FILES_WITH_REALPATH
#endif
static THREAD_RETURN_TYPE STDCALL_ON_WIN32 connectionHandlerThread(void* connectionPointer);
static struct Response* createResponseForRequestAutoreleased(const struct Request* request, struct Connection* connection);
typedef enum {
URLDecodeTypeWholeURL,
URLDecodeTypeParameter
} URLDecodeType;
static bool URLDecode(const char* encoded, char* decoded, size_t decodedCapacity, size_t* decodedLength, URLDecodeType type);
const struct Header* headerInRequest(const char* headerName, const struct Request* request) {
for (size_t i = 0; i < request->headersCount; i++) {
assert(NULL != request->headers[i].name.contents);
if (0 == strcasecmp(request->headers[i].name.contents, headerName)) {
return &request->headers[i];
}
}
return NULL;
}
static char* strdupIfNotNull(const char* strToDup) {
if (NULL == strToDup) {
return NULL;
}
return strdup(strToDup);
}
typedef enum {
URLDecodeStateNormal,
URLDecodeStatePercentFirstDigit,
URLDecodeStatePercentSecondDigit
} URLDecodeState;
char* strdupDecodeGETorPOSTParam(const char* paramNameIncludingEquals, const char* paramString, const char* valueIfNotFound) {
assert(strstr(paramNameIncludingEquals, "=") != NULL && "You have to pass an equals sign after the param name, like 'name='");
/* The string passed is actually NULL -- this is accepted because it's more convenient */
if (NULL == paramString) {
return strdupIfNotNull(valueIfNotFound);
}
/* Find the paramString ("name=") */
const char* paramStart = strstr(paramString, paramNameIncludingEquals);
if (NULL == paramStart) {
return strdupIfNotNull(valueIfNotFound);
}
/* Ok paramStart points at -->"name=" ; let's make it point at "=" */
paramStart = strstr(paramStart, "=");
if (NULL == paramStart) {
ews_printf("It's very suspicious that we couldn't find an equals sign after searching for '%s' in '%s'\n", paramStart, paramString);
return strdupIfNotNull(valueIfNotFound);
}
/* We need to skip past the "=" */
paramStart++;
/* Oh man! End of string is right here */
if ('\0' == *paramStart) {
char* empty = (char*) malloc(1);
empty[0] = '\0';
return empty;
}
size_t maximumPossibleLength = strlen(paramStart);
char* decoded = (char*) malloc(maximumPossibleLength + 1);
bool decodeSuccess = URLDecode(paramStart, decoded, maximumPossibleLength + 1, NULL, URLDecodeTypeParameter);
if (!decodeSuccess) {
ews_printf("Failed to decode URL parameter %s due to lack of space. This is strange because we asked for %zu capacity for the decoded string", paramNameIncludingEquals, maximumPossibleLength);
}
return decoded;
}
char* strdupDecodeGETParam(const char* paramNameIncludingEquals, const struct Request* request, const char* valueIfNotFound) {
return strdupDecodeGETorPOSTParam(paramNameIncludingEquals, request->path, valueIfNotFound);
}
char* strdupDecodePOSTParam(const char* paramNameIncludingEquals, const struct Request* request, const char* valueIfNotFound) {
return strdupDecodeGETorPOSTParam(paramNameIncludingEquals, request->body.contents, valueIfNotFound);
}
typedef enum {
PathStateNormal,
PathStateSep,
PathStateDot,
} PathState;
/* Aggressively escape strings for URLs. This adds the %12%0f stuff */
static char* strdupEscapeForURL(const char* stringToEscape) {
struct HeapString escapedString;
heapStringInit(&escapedString);
const char* p = stringToEscape;
while ('\0' != *p) {
bool isULetter = *p >= 'A' && *p <= 'Z';
bool isLLetter = *p >= 'a' && *p <= 'z';
bool isNumber = *p >= '0' && *p <= '9';
bool isAcceptablePunctuation = ('.' == *p || '/' == *p || '-' == *p);
if (isULetter || isLLetter || isNumber || isAcceptablePunctuation) {
heapStringAppendChar(&escapedString, *p);
} else {
// huh I guess %02x doesn't work in Windows?? holy cow
uint8_t pu8 = (uint8_t)*p;
uint8_t firstDigit = (pu8 & 0xf0) >> 4;
uint8_t secondDigit = pu8 & 0xf;
heapStringAppendFormat(&escapedString, "%%%x%x", firstDigit, secondDigit);
}
p++;
}
return escapedString.contents;
}
char* strdupEscapeForHTML(const char* stringToEscape) {
struct HeapString escapedString;
heapStringInit(&escapedString);
size_t stringToEscapeLength = strlen(stringToEscape);
if (0 == stringToEscapeLength) {
char* empty = (char*) malloc(1);
*empty = '\0';
return empty;
}
for (size_t i = 0; i < stringToEscapeLength; i++) {
// this is an excerpt of some things translated by the PHP htmlentities function
char c = stringToEscape[i];
switch (c) {
case '"':
heapStringAppendFormat(&escapedString, """);
break;
case '&':
heapStringAppendFormat(&escapedString, "&");
break;
case '\'':
heapStringAppendFormat(&escapedString, "'");
break;
case '<':
heapStringAppendFormat(&escapedString, "<");
break;
case '>':
heapStringAppendFormat(&escapedString, ">");
break;
case ' ':
heapStringAppendFormat(&escapedString, " ");
break;
default:
heapStringAppendChar(&escapedString, c);
break;
}
}
return escapedString.contents;
}
/* Is someone using ../ to try to read a directory outside of the documentRoot? */
static bool pathEscapesDocumentRoot(const char* path) {
int subdirDepth = 0;
PathState state = PathStateNormal;
bool isFirstChar = true;
while ('\0' != *path) {
switch (state) {
case PathStateNormal:
if ('/' == *path || '\\' == *path) {
state = PathStateSep;
} else if (isFirstChar && '.' == *path) {
state = PathStateDot;
} else if (isFirstChar) {
subdirDepth++;
}
isFirstChar = false;
break;
case PathStateSep:
if ('.' == *path) {
state = PathStateDot;
} else if ('/' != *path && '\\' != *path) {
subdirDepth++;
state = PathStateNormal;
}
break;
case PathStateDot:
if ('/' == *path) {
state = PathStateSep;
} else if ('.' == *path) {
subdirDepth--;
state = PathStateNormal;
} else {
state = PathStateNormal;
}
break;
}
path++;
}
if (subdirDepth < 0) {
return true;
}
return false;
}
static bool URLDecode(const char* encoded, char* decoded, size_t decodedCapacity, size_t* decodedLength, URLDecodeType type) {
/* A URL encoded string of length N should always be able to fit into a decoded string <= N */
/* We found a value. Unescape the URL. This is probably filled with bugs */
size_t deci = 0;
/* these three vars unescape % escaped things */
URLDecodeState state = URLDecodeStateNormal;
char firstDigit = '\0';
char secondDigit;
bool capacityExhausted = false;
size_t encodedLength = 0;
while (1) {
/* break out the exit conditions */
/* nothing left in the encoding string to process */
if (*encoded == '\0') {
break;
}
/* If we are decoding only a parameter then stop at & */
if (*encoded == '&' && URLDecodeTypeParameter == type) {
break;
}
/* need to store a null char in decoded[decodedCapacity - 1] */
if (deci >= decodedCapacity - 1) {
/* Note that the capacity is only exhausted if we have more to process.
Thus it is the last check before '\0' and '&'. */
capacityExhausted = true;
break;
}
switch (state) {
case URLDecodeStateNormal:
if ('%' == *encoded) {
state = URLDecodeStatePercentFirstDigit;
} else if ('+' == *encoded) {
decoded[deci] = ' ';
deci++;
} else {
decoded[deci] = *encoded;
deci++;
}
break;
case URLDecodeStatePercentFirstDigit:
// copy the first digit, get the second digit
firstDigit = *encoded;
state = URLDecodeStatePercentSecondDigit;
break;
case URLDecodeStatePercentSecondDigit:
{
secondDigit = *encoded;
int decodedEscape;
char hexString[] = {firstDigit, secondDigit, '\0'};
int items = sscanf(hexString, "%02x", &decodedEscape);
if (1 == items) {
decoded[deci] = (char) decodedEscape;
deci++;
} else {
ews_printf("Warning: Unable to decode hex string 0x%s from %s", hexString, encoded);
}
state = URLDecodeStateNormal;
}
break;
}
encoded++;
encodedLength++;
}
decoded[deci] = '\0';
if (NULL != decodedLength) {
*decodedLength = deci;
}
if (capacityExhausted) {
ews_printf_debug("URLDecode: A string of length %zu could not be decoded into a string of capacity %zu\n", encodedLength, decodedCapacity);
return false;
}
return true;
}
static void heapStringReallocIfNeeded(struct HeapString* string, size_t minimumCapacity) {
if (minimumCapacity <= string->capacity) {
return;
}
/* to avoid many reallocations every time we call AppendChar, round up to the next power of two */
string->capacity = heapStringNextAllocationSize(minimumCapacity);
assert(string->capacity > 0 && "We are about to allocate a string with 0 capacity. We should have checked this condition above");
bool previouslyAllocated = string->contents != NULL;
/* Sometimes string->contents is NULL. realloc handles that case so no need for an extra if (NULL) malloc else realloc */
string->contents = (char*) realloc(string->contents, string->capacity);
/* zero out the newly allocated memory */
memset(&string->contents[string->length], 0, string->capacity - string->length);
if (OptionIncludeStatusPageAndCounters) {
pthread_mutex_lock(&counters.lock);
if (previouslyAllocated) {
counters.heapStringReallocations++;
} else {
counters.heapStringAllocations++;
}
counters.heapStringTotalBytesReallocated += string->capacity;
pthread_mutex_unlock(&counters.lock);
}
}
static size_t heapStringNextAllocationSize(size_t required) {
/* start with something reasonalbe that responses could fit into. The idea here
is to avoid constant reallocation when dynamically building responses. */
size_t powerOf2 = 256;
while (powerOf2 < required) {
powerOf2 *= 2;
}
return powerOf2;
}
void heapStringAppendChar(struct HeapString* string, char c) {
heapStringReallocIfNeeded(string, string->length + 2);
string->contents[string->length] = c;
string->length++;
/* this should already be null-terminated but we'll be extra safe for web scale ^_^ */
string->contents[string->length] = '\0';
}
void heapStringAppendFormat(struct HeapString* string, const char* format, ...) {
va_list ap;
va_start(ap, format);
heapStringAppendFormatV(string, format, ap);
va_end(ap);
}
void heapStringSetToCString(struct HeapString* heapString, const char* cString) {
size_t cStringLength = strlen(cString);
heapStringReallocIfNeeded(heapString, cStringLength + 1);
memcpy(heapString->contents, cString, cStringLength);
heapString->length = cStringLength;
heapString->contents[heapString->length] = '\0';
}
void heapStringAppendString(struct HeapString* string, const char* stringToAppend) {
size_t stringToAppendLength = strlen(stringToAppend);
/* just exit early if the string length is too small */
if (0 == stringToAppendLength) {
return;
}
/* realloc and append the string */
size_t requiredCapacity = stringToAppendLength + string->length + 1;
heapStringReallocIfNeeded(string, requiredCapacity);
memcpy(&string->contents[string->length], stringToAppend, stringToAppendLength);
string->length += stringToAppendLength;
string->contents[string->length] = '\0';
}
void heapStringAppendHeapString(struct HeapString* target, const struct HeapString* source) {
heapStringReallocIfNeeded(target, target->length + source->length + 1);
memcpy(&target->contents[target->length], source->contents, source->length);
target->length += source->length;
target->contents[target->length] = '\0';
}
static bool heapStringIsSaneCString(const struct HeapString* heapString) {
if (NULL == heapString->contents) {
if (heapString->capacity != 0) {
ews_printf("The heap string %p has a capacity of %" PRIu64 "but it's contents are NULL\n", heapString, (uint64_t) heapString->capacity);
return false;
}
if (heapString->length != 0) {
ews_printf("The heap string %p has a length of %" PRIu64 " but capacity is 0 and contents are NULL\n", heapString, (uint64_t) heapString->capacity);
return false;
}
return true;
}
if (heapString->capacity <= heapString->length) {
ews_printf("Heap string %p has probably overwritten invalid memory because the capacity (%" PRIu64 ") is <= length (%" PRIu64 "), which is a big nono. The capacity must always be 1 more than the length since the contents is null-terminated for convenience.\n",
heapString, (uint64_t) heapString->capacity, (uint64_t)heapString->length);
return false;
}
if (strlen(heapString->contents) != heapString->length) {
ews_printf("The %p strlen(heap string contents) (%" PRIu64 ") is not equal to heapString length (%" PRIu64 "), which is not correct for a C string. This can be correct if we're sending something like a PNG image which can contain '\\0' characters",
heapString,
(uint64_t) strlen(heapString->contents),
(uint64_t) heapString->length);
return false;
}
return true;
}
void heapStringAppendFormatV(struct HeapString* string, const char* format, va_list ap) {
/* Figure out how many characters it would take to print the string */
va_list apCopy;
va_copy(apCopy, ap);
size_t appendLength = vsnprintf(NULL, 0, format, ap);
size_t requiredCapacity = string->length + appendLength + 1;
heapStringReallocIfNeeded(string, requiredCapacity);
assert(string->capacity >= string->length + appendLength + 1);
/* perform the actual vsnprintf that does the work */
size_t actualAppendLength = vsnprintf(&string->contents[string->length], string->capacity - string->length, format, apCopy);
string->length += appendLength;
assert(actualAppendLength == appendLength && "We called vsnprintf twice with the same format and value arguments and got different string lengths");
/* explicitly null terminate in case I messed up the vsnprinf logic */
string->contents[string->length] = '\0';
}
void heapStringInit(struct HeapString* string) {
string->capacity = 0;
string->contents = NULL;
string->length = 0;
}
void heapStringFreeContents(struct HeapString* string) {
if (NULL != string->contents) {
assert(string->capacity > 0 && "A heap string had a capacity > 0 with non-NULL contents which implies a malloc(0)");
free(string->contents);
string->contents = NULL;
string->capacity = 0;
string->length = 0;
if (OptionIncludeStatusPageAndCounters) {
pthread_mutex_lock(&counters.lock);
counters.heapStringFrees++;
pthread_mutex_unlock(&counters.lock);
}
} else {
assert(string->capacity == 0 && "Why did a string with a NULL contents have a capacity > 0? This is not correct and may indicate corruption");
}
}
struct HeapString connectionDebugStringCreate(const struct Connection* connection) {
struct HeapString debugString;
heapStringInit(&debugString);
heapStringAppendFormat(&debugString, "%s %s from %s:%s\n", connection->request.method, connection->request.path, connection->remoteHost, connection->remotePort);
heapStringAppendFormat(&debugString, "Request URL Path decoded to '%s'\n", connection->request.pathDecoded);
heapStringAppendFormat(&debugString, "Bytes sent:%" PRId64 "\n", connection->status.bytesSent);
heapStringAppendFormat(&debugString, "Bytes received:%" PRId64 "\n", connection->status.bytesReceived);
heapStringAppendFormat(&debugString, "Final request parse state:%d\n", connection->request.state);
heapStringAppendFormat(&debugString, "Header pool used:%" PRIu64 "\n", (uint64_t) connection->request.headersStringPoolOffset);
heapStringAppendFormat(&debugString, "Header count:%" PRIu64 "\n", (uint64_t) connection->request.headersCount);
bool firstHeader = true;
heapStringAppendString(&debugString, "\n*** Request Headers ***\n");
for (size_t i = 0; i < connection->request.headersCount; i++) {
if (firstHeader) {
firstHeader = false;
}
const struct Header* header = &connection->request.headers[i];
heapStringAppendFormat(&debugString, "'%s' = '%s'\n", header->name.contents, header->value.contents);
}
if (NULL != connection->request.body.contents) {
heapStringAppendFormat(&debugString, "\n*** Request Body ***\n%s\n", connection->request.body.contents);
}
heapStringAppendFormat(&debugString, "\n*** Request Warnings ***\n");
bool hadWarnings = false;
if (connection->request.warnings.headersStringPoolExhausted) {
heapStringAppendString(&debugString, "headersStringPoolExhausted - try upping REQUEST_HEADERS_MAX_MEMORY\n");
hadWarnings = true;
}
if (connection->request.warnings.tooManyHeaders) {
heapStringAppendString(&debugString, "tooManyHeaders - try upping REQUEST_MAX_HEADERS\n");
hadWarnings = true;
}
if (connection->request.warnings.methodTruncated) {
heapStringAppendString(&debugString, "methodTruncated - you can increase the size of method[]\n");
hadWarnings = true;
}
if (connection->request.warnings.pathTruncated) {
heapStringAppendString(&debugString, "pathTruncated - you can increase the size of path[]\n");
hadWarnings = true;
}
if (connection->request.warnings.versionTruncated) {
heapStringAppendString(&debugString, "versionTruncated - you can increase the size of version[]\n");
hadWarnings = true;
}
if (connection->request.warnings.bodyTruncated) {
heapStringAppendString(&debugString, "bodyTruncated - you can increase REQUEST_MAX_BODY_LENGTH");
hadWarnings = true;
}
if (!hadWarnings) {
heapStringAppendString(&debugString, "No warnings\n");
}
return debugString;
}
static void poolStringStartNewString(struct PoolString* poolString, struct Request* request) {
/* always re-initialize the length...just in case */
poolString->length = 0;
/* this is the first string in the pool */
if (0 == request->headersStringPoolOffset) {
poolString->contents = request->headersStringPool;
return;
}
/* the pool string is full - don't initialize anything writable and ensure any writing to this pool string crashes */
if (request->headersStringPoolOffset >= REQUEST_HEADERS_MAX_MEMORY - 1 - sizeof('\0')) { // we need to store one character (-1) and a null character at the end of the last string sizeof('\0')
poolString->length = 0;
poolString->contents = NULL;
request->warnings.headersStringPoolExhausted = true;
return;
}
/* there's already another string in the pool - we need to skip the null character (the string pool is initialized to 0s by calloc) */
request->headersStringPoolOffset++;
poolString->contents = &request->headersStringPool[request->headersStringPoolOffset];
poolString->length = 0;
}
static void poolStringAppendChar(struct Request* request, struct PoolString* string, char c) {
if (request->headersStringPoolOffset >= REQUEST_HEADERS_MAX_MEMORY - 1 - sizeof('\0')) { // we need to store one character (-1) and a null character at the end of the last string sizeof('\0')
request->warnings.headersStringPoolExhausted = true;
return;
}
string->contents[string->length] = c;
string->length++;
request->headersStringPoolOffset++;
}
// allocates a response with content = malloc(contentLength + 1) so you can write null-terminated strings to it
struct Response* responseAlloc(int code, const char* status, const char* contentType, size_t bodyCapacity) {
struct Response* response = (struct Response*) calloc(1, sizeof(*response));
response->code = code;
heapStringInit(&response->body);
response->body.capacity = bodyCapacity;
response->body.length = 0;
if (response->body.capacity > 0) {
response->body.contents = (char*) calloc(1, response->body.capacity);
if (OptionIncludeStatusPageAndCounters) {
pthread_mutex_lock(&counters.lock);
counters.heapStringAllocations++;
pthread_mutex_unlock(&counters.lock);
}
}
response->contentType = strdupIfNotNull(contentType);
response->status = strdupIfNotNull(status);
return response;
}
struct Response* responseAllocHTML(const char* html) {
return responseAllocHTMLWithStatus(200, "OK", html);
}
struct Response* responseAllocHTMLWithFormat(const char* format, ...) {
struct Response* response = responseAlloc(200, "OK", "text/html; charset=UTF-8", 0);
va_list ap;
va_start(ap, format);
heapStringAppendFormatV(&response->body, format, ap);
va_end(ap);
return response;
}
struct Response* responseAllocHTMLWithStatus(int code, const char* status, const char* html) {
struct Response* response = responseAlloc(code, status, "text/html; charset=UTF-8", 0);
heapStringSetToCString(&response->body, html);
return response;
}
struct Response* responseAllocJSON(const char* json) {
return responseAllocJSONWithStatus(200, "OK", json);
}
struct Response* responseAllocJSONWithFormat(const char* format, ...) {
struct Response* response = responseAlloc(200, "OK", "application/json", 0);
va_list ap;
va_start(ap, format);
heapStringAppendFormatV(&response->body, format, ap);
va_end(ap);
return response;
}
struct Response* responseAllocJSONWithStatus(int code, const char* status, const char* json) {
struct Response* response = responseAlloc(code, status, "application/json", 0);
heapStringSetToCString(&response->body, json);
return response;
}