1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
22 ***************************************************************************/
24 /* sws.c: simple (silly?) web server
26 This code was originally graciously donated to the project by Juergen
27 Wilke. Thanks a bunch!
30 #include "setup.h" /* portability help from the lib directory */
39 #include <sys/types.h>
44 #ifdef HAVE_SYS_SOCKET_H
45 #include <sys/socket.h>
47 #ifdef HAVE_NETINET_IN_H
48 #include <netinet/in.h>
50 #ifdef _XOPEN_SOURCE_EXTENDED
51 /* This define is "almost" required to build on HPUX 11 */
52 #include <arpa/inet.h>
58 #include "curlx.h" /* from the private lib dir */
72 #if defined(WIN32) && !defined(__CYGWIN__)
77 #define sleep(sec) Sleep ((sec)*1000)
79 #define EINPROGRESS WSAEINPROGRESS
80 #define EWOULDBLOCK WSAEWOULDBLOCK
81 #define EISCONN WSAEISCONN
82 #define ENOTSOCK WSAENOTSOCK
83 #define ECONNREFUSED WSAECONNREFUSED
85 static void win32_cleanup(void);
87 #if defined(ENABLE_IPV6) && defined(__MINGW32__)
88 const struct in6_addr in6addr_any = {{ IN6ADDR_ANY_INIT }};
92 /* include memdebug.h last */
95 #define REQBUFSIZ 150000
96 #define REQBUFSIZ_TXT "149999"
98 long prevtestno=-1; /* previous test number we served */
99 long prevpartno=-1; /* previous part number we served */
100 bool prevbounce; /* instructs the server to increase the part number for
101 a test in case the identical testno+partno request
105 char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */
106 int offset; /* size of the incoming request */
107 long testno; /* test number found in the request */
108 long partno; /* part number found in the request */
109 int open; /* keep connection open info, as found in the request */
110 bool auth_req; /* authentication required, don't wait for body unless
111 there's an Authorization header */
112 bool auth; /* Authorization header present in the incoming request */
113 size_t cl; /* Content-Length of the incoming request */
114 bool digest; /* Authorization digest header found */
115 bool ntlm; /* Authorization ntlm header found */
118 int ProcessRequest(struct httprequest *req);
119 void storerequest(char *reqbuf);
121 #define DEFAULT_PORT 8999
123 #ifndef DEFAULT_LOGFILE
124 #define DEFAULT_LOGFILE "log/sws.log"
127 #define SWSVERSION "cURL test suite HTTP server/0.1"
129 #define REQUEST_DUMP "log/server.input"
130 #define RESPONSE_DUMP "log/server.response"
132 #define TEST_DATA_PATH "%s/data/test%ld"
134 /* very-big-path support */
135 #define MAXDOCNAMELEN 140000
136 #define MAXDOCNAMELEN_TXT "139999"
138 #define REQUEST_KEYWORD_SIZE 256
140 #define CMD_AUTH_REQUIRED "auth_required"
142 #define END_OF_HEADERS "\r\n\r\n"
144 /* global variable, where to find the 'data' dir */
145 const char *path=".";
148 DOCNUMBER_NOTHING = -7,
150 DOCNUMBER_BADCONNECT = -5,
151 DOCNUMBER_INTERNAL= -4,
152 DOCNUMBER_CONNECT = -3,
153 DOCNUMBER_WERULEZ = -2,
158 /* sent as reply to a QUIT */
159 static const char *docquit =
160 "HTTP/1.1 200 Goodbye" END_OF_HEADERS;
162 /* sent as reply to a CONNECT */
163 static const char *docconnect =
164 "HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;
166 /* sent as reply to a "bad" CONNECT */
167 static const char *docbadconnect =
168 "HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS;
170 /* send back this on 404 file not found */
171 static const char *doc404 = "HTTP/1.1 404 Not Found\r\n"
172 "Server: " SWSVERSION "\r\n"
173 "Connection: close\r\n"
174 "Content-Type: text/html"
176 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
178 "<TITLE>404 Not Found</TITLE>\n"
180 "<H1>Not Found</H1>\n"
181 "The requested URL was not found on this server.\n"
182 "<P><HR><ADDRESS>" SWSVERSION "</ADDRESS>\n" "</BODY></HTML>\n";
185 static volatile int sigpipe; /* Why? It's not used */
188 static void logmsg(const char *msg, ...)
190 time_t t = time(NULL);
192 struct tm *curr_time = localtime(&t);
193 char buffer[256]; /* possible overflow if you pass in a huge string */
197 vsprintf(buffer, msg, ap);
200 logfp = fopen(DEFAULT_LOGFILE, "a");
202 fprintf(logfp?logfp:stderr, /* write to stderr if the logfile doesn't open */
203 "%02d:%02d:%02d %s\n",
206 curr_time->tm_sec, buffer);
213 static void sigpipe_handler(int sig)
215 (void)sig; /* prevent warning */
220 #if defined(WIN32) && !defined(__CYGWIN__)
222 #define perror(m) win32_perror(m)
224 static void win32_perror (const char *msg)
227 DWORD err = WSAGetLastError();
229 if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
230 LANG_NEUTRAL, buf, sizeof(buf), NULL))
231 snprintf(buf, sizeof(buf), "Unknown error %lu (%#lx)", err, err);
233 fprintf(stderr, "%s: ", msg);
234 fprintf(stderr, "%s\n", buf);
238 static char *test2file(long testno)
240 static char filename[256];
241 sprintf(filename, TEST_DATA_PATH, path, testno);
246 int ProcessRequest(struct httprequest *req)
248 char *line=req->reqbuf;
250 static char request[REQUEST_KEYWORD_SIZE];
251 static char doc[MAXDOCNAMELEN];
253 int prot_major, prot_minor;
255 end = strstr(req->reqbuf, END_OF_HEADERS);
257 /* try to figure out the request characteristics as soon as possible, but
259 if((req->testno == DOCNUMBER_NOTHING) &&
260 sscanf(line, "%" REQBUFSIZ_TXT"s %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
267 /* find the last slash */
268 ptr = strrchr(doc, '/');
270 /* get the number after it */
275 if((strlen(doc) + strlen(request)) < 200)
276 sprintf(logbuf, "Got request: %s %s HTTP/%d.%d",
277 request, doc, prot_major, prot_minor);
279 sprintf(logbuf, "Got a *HUGE* request HTTP/%d.%d",
280 prot_major, prot_minor);
283 if(!strncmp("/verifiedserver", ptr, 15)) {
284 logmsg("Are-we-friendly question received");
285 req->testno = DOCNUMBER_WERULEZ;
289 if(!strncmp("/quit", ptr, 15)) {
290 logmsg("Request-to-quit received");
291 req->testno = DOCNUMBER_QUIT;
295 ptr++; /* skip the slash */
297 req->testno = strtol(ptr, &ptr, 10);
299 if(req->testno > 10000) {
300 req->partno = req->testno % 10000;
301 req->testno /= 10000;
306 sprintf(logbuf, "Requested test number %ld part %ld",
307 req->testno, req->partno);
311 filename = test2file(req->testno);
313 stream=fopen(filename, "rb");
315 logmsg("Couldn't open test file %d", req->testno);
316 req->open = FALSE; /* closes connection */
323 /* get the custom server control "commands" */
324 cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize);
328 logmsg("Found a reply-servercmd section!");
330 if(!strncmp(CMD_AUTH_REQUIRED, cmd, strlen(CMD_AUTH_REQUIRED))) {
331 logmsg("instructed to require authorization header");
332 req->auth_req = TRUE;
339 if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
340 doc, &prot_major, &prot_minor) == 3) {
341 sprintf(logbuf, "Receiced a CONNECT %s HTTP/%d.%d request",
342 doc, prot_major, prot_minor);
345 if(prot_major*10+prot_minor == 10)
346 req->open = FALSE; /* HTTP 1.0 closes connection by default */
348 if(!strncmp(doc, "bad", 3))
349 /* if the host name starts with bad, we fake an error here */
350 req->testno = DOCNUMBER_BADCONNECT;
351 else if(!strncmp(doc, "test", 4)) {
352 /* if the host name starts with test, the port number used in the
353 CONNECT line will be used as test number! */
354 char *ptr = strchr(doc, ':');
356 req->testno = atoi(ptr+1);
358 req->testno = DOCNUMBER_CONNECT;
361 req->testno = DOCNUMBER_CONNECT;
364 logmsg("Did not find test number in PATH");
365 req->testno = DOCNUMBER_404;
371 /* we don't have a complete request yet! */
374 /* **** Persistancy ****
376 * If the request is a HTTP/1.0 one, we close the connection unconditionally
379 * If the request is a HTTP/1.1 one, we MUST check for a "Connection:"
380 * header that might say "close". If it does, we close a connection when
381 * this request is processed. Otherwise, we keep the connection alive for X
386 if(!req->cl && curlx_strnequal("Content-Length:", line, 15)) {
387 /* If we don't ignore content-length, we read it and we read the whole
388 request including the body before we return. If we've been told to
389 ignore the content-length, we will return as soon as all headers
390 have been received */
391 req->cl = strtol(line+15, &line, 10);
393 logmsg("Found Content-Length: %d in the request", req->cl);
396 else if(curlx_strnequal("Transfer-Encoding: chunked", line,
397 strlen("Transfer-Encoding: chunked"))) {
398 /* chunked data coming in */
403 if(strstr(req->reqbuf, "\r\n0\r\n"))
404 /* end of chunks reached */
407 return 0; /* not done */
410 line = strchr(line, '\n');
415 if(!req->auth && strstr(req->reqbuf, "Authorization:")) {
416 req->auth = TRUE; /* Authorization: header present! */
418 logmsg("Authorization header found, as required");
421 if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
422 /* If the client is passing this Digest-header, we set the part number
423 to 1000. Not only to spice up the complexity of this, but to make
424 Digest stuff to work in the test suite. */
426 req->digest = TRUE; /* header found */
427 logmsg("Received Digest request, sending back data %d", req->partno);
429 else if(!req->ntlm &&
430 strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) {
431 /* If the client is passing this type-3 NTLM header */
433 req->ntlm = TRUE; /* NTLM found */
434 logmsg("Received NTLM type-3, xxxxxxxxxxxxx sending back data %d", req->partno);
436 logmsg(" Expecting %d POSTed bytes", req->cl);
439 else if(!req->ntlm &&
440 strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) {
441 /* If the client is passing this type-1 NTLM header */
443 req->ntlm = TRUE; /* NTLM found */
444 logmsg("Received NTLM type-1, sending back data %d", req->partno);
446 if(strstr(req->reqbuf, "Connection: close"))
447 req->open = FALSE; /* close connection after this request */
449 /* If authentication is required and no auth was provided, end now. This
450 makes the server NOT wait for PUT/POST data and you can then make the
451 test case send a rejection before any such data has been sent. Test case
453 if(req->auth_req && !req->auth)
457 if(req->cl <= strlen(end+strlen(END_OF_HEADERS)))
460 return 0; /* not complete yet */
465 /* store the entire request in a file */
466 void storerequest(char *reqbuf)
470 dump = fopen(REQUEST_DUMP, "ab"); /* b is for windows-preparing */
472 size_t len = strlen(reqbuf);
473 fwrite(reqbuf, 1, len, dump);
476 logmsg("Wrote request (%d bytes) input to " REQUEST_DUMP,
480 logmsg("Failed to write request input to " REQUEST_DUMP);
484 /* return 0 on success, non-zero on failure */
485 static int get_request(int sock, struct httprequest *req)
488 char *reqbuf = req->reqbuf;
490 /*** Init the httpreqest structure properly for the upcoming request ***/
491 memset(req, 0, sizeof(struct httprequest));
493 /* here's what should not be 0 from the start */
494 req->testno = DOCNUMBER_NOTHING; /* safe default */
495 req->open = TRUE; /* connection should remain open and wait for more
498 /*** end of httprequest init ***/
500 while (req->offset < REQBUFSIZ) {
501 int got = sread(sock, reqbuf + req->offset, REQBUFSIZ - req->offset);
505 logmsg("recv() returned error");
506 return DOCNUMBER_INTERNAL;
508 logmsg("Connection closed by client");
509 reqbuf[req->offset]=0;
511 /* dump the request receivied so far to the external file */
512 storerequest(reqbuf);
513 return DOCNUMBER_INTERNAL;
517 reqbuf[req->offset] = 0;
519 if(ProcessRequest(req))
523 if (req->offset >= REQBUFSIZ) {
524 logmsg("Request buffer overflow, closing connection");
525 reqbuf[REQBUFSIZ-1]=0;
527 /* dump the request to an external file anyway */
530 reqbuf[req->offset]=0;
532 /* dump the request to an external file */
533 storerequest(reqbuf);
535 return fail; /* success */
538 /* returns -1 on failure */
539 static int send_doc(int sock, struct httprequest *req)
549 int persistant = TRUE;
552 static char weare[256];
554 char partbuf[80]="data";
558 logmsg("Send response number %d part %d", req->testno, req->partno);
560 if(req->testno < 0) {
561 switch(req->testno) {
563 logmsg("Replying to QUIT");
566 case DOCNUMBER_WERULEZ:
567 /* we got a "friends?" question, reply back that we sure are */
568 logmsg("Identifying ourselves as friends");
569 sprintf(weare, "HTTP/1.1 200 OK\r\n\r\nWE ROOLZ: %d\r\n",
573 case DOCNUMBER_INTERNAL:
574 logmsg("Bailing out due to internal error");
576 case DOCNUMBER_CONNECT:
577 logmsg("Replying to CONNECT");
580 case DOCNUMBER_BADCONNECT:
581 logmsg("Replying to a bad CONNECT");
582 buffer = docbadconnect;
586 logmsg("Replying to with a 404");
593 count = strlen(buffer);
596 char *filename = test2file(req->testno);
599 sprintf(partbuf, "data%ld", req->partno);
601 stream=fopen(filename, "rb");
603 logmsg("Couldn't open test file");
607 buffer = spitout(stream, "reply", partbuf, &count);
608 ptr = (char *)buffer;
612 /* re-open the same file again */
613 stream=fopen(filename, "rb");
615 logmsg("Couldn't open test file");
619 /* get the custom server control "commands" */
620 cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize);
625 dump = fopen(RESPONSE_DUMP, "ab"); /* b is for windows-preparing */
627 logmsg("couldn't create logfile: " RESPONSE_DUMP);
631 /* If the word 'swsclose' is present anywhere in the reply chunk, the
632 connection will be closed after the data has been sent to the requesting
634 if(strstr(buffer, "swsclose") || !count) {
636 logmsg("connection close instruction \"swsclose\" found in response");
638 if(strstr(buffer, "swsbounce")) {
640 logmsg("enable \"swsbounce\" in the next request");
646 responsesize = count;
648 written = swrite(sock, buffer, count);
650 logmsg("Sending response failed and we bailed out!");
653 /* write to file as well */
654 fwrite(buffer, 1, written, dump);
662 logmsg("Response sent (%d bytes) and written to " RESPONSE_DUMP,
673 if(2 == sscanf(ptr, "%31s %d", command, &num)) {
674 if(!strcmp("wait", command)) {
675 logmsg("Told to sleep for %d seconds", num);
676 sleep(num); /* wait this many seconds */
679 logmsg("Unknown command in reply command section");
681 ptr = strchr(ptr, '\n');
686 } while(ptr && *ptr);
691 req->open = persistant;
693 prevtestno = req->testno;
694 prevpartno = req->partno;
699 #if defined(WIN32) && !defined(__CYGWIN__)
700 static void win32_init(void)
702 WORD wVersionRequested;
705 wVersionRequested = MAKEWORD(2, 0);
707 err = WSAStartup(wVersionRequested, &wsaData);
710 perror("Winsock init failed");
711 logmsg("Error initialising winsock -- aborting\n");
715 if ( LOBYTE( wsaData.wVersion ) != 2 ||
716 HIBYTE( wsaData.wVersion ) != 0 ) {
719 perror("Winsock init failed");
720 logmsg("No suitable winsock.dll found -- aborting\n");
724 static void win32_cleanup(void)
732 int main(int argc, char *argv[])
734 struct sockaddr_in me;
736 struct sockaddr_in6 me6;
737 #endif /* ENABLE_IPV6 */
738 int sock, msgsock, flag;
739 unsigned short port = DEFAULT_PORT;
741 char *pidname= (char *)".http.pid";
742 struct httprequest req;
747 if(!strcmp("--version", argv[arg])) {
748 printf("sws IPv4%s\n",
757 else if(!strcmp("--pidfile", argv[arg])) {
760 pidname = argv[arg++];
762 else if(!strcmp("--ipv6", argv[arg])) {
771 port = (unsigned short)atoi(argv[arg++]);
778 #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
780 atexit(win32_cleanup);
785 signal(SIGPIPE, sigpipe_handler);
787 #ifdef HAVE_SIGINTERRUPT
788 siginterrupt(SIGPIPE, 1);
796 sock = socket(AF_INET, SOCK_STREAM, 0);
799 sock = socket(AF_INET6, SOCK_STREAM, 0);
803 perror("opening stream socket");
804 logmsg("Error opening socket");
810 (sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &flag,
812 perror("setsockopt(SO_REUSEADDR)");
818 me.sin_family = AF_INET;
819 me.sin_addr.s_addr = INADDR_ANY;
820 me.sin_port = htons(port);
821 rc = bind(sock, (struct sockaddr *) &me, sizeof(me));
825 memset(&me6, 0, sizeof(struct sockaddr_in6));
826 me6.sin6_family = AF_INET6;
827 me6.sin6_addr = in6addr_any;
828 me6.sin6_port = htons(port);
829 rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6));
831 #endif /* ENABLE_IPV6 */
833 perror("binding stream socket");
834 logmsg("Error binding socket");
838 pidfile = fopen(pidname, "w");
840 fprintf(pidfile, "%d\n", (int)getpid());
844 fprintf(stderr, "Couldn't write pid file\n");
846 logmsg("Running IPv%d version",
854 /* start accepting connections */
858 msgsock = accept(sock, NULL, NULL);
863 logmsg("====> Client connect");
866 if(get_request(msgsock, &req))
867 /* non-zero means error, break out of loop */
871 /* bounce treatment requested */
872 if((req.testno == prevtestno) &&
873 (req.partno == prevpartno)) {
875 logmsg("BOUNCE part number to %ld", req.partno);
879 send_doc(msgsock, &req);
881 if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
882 logmsg("special request received, no persistancy");
886 logmsg("instructed to close connection after server-reply");
891 logmsg("=> persistant connection request ended, awaits new request");
892 /* if we got a CONNECT, loop and get another request as well! */
893 } while(req.open || (req.testno == DOCNUMBER_CONNECT));
895 logmsg("====> Client disconnect");
898 if (req.testno == DOCNUMBER_QUIT)