fix compiler warning: defined but not used
[platform/upstream/curl.git] / tests / server / sws.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
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.
13  *
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.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * $Id$
22  ***************************************************************************/
23
24 /* sws.c: simple (silly?) web server
25
26    This code was originally graciously donated to the project by Juergen
27    Wilke. Thanks a bunch!
28
29  */
30 #include "setup.h" /* portability help from the lib directory */
31
32 #ifdef HAVE_SIGNAL_H
33 #include <signal.h>
34 #endif
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #ifdef HAVE_SYS_SOCKET_H
39 #include <sys/socket.h>
40 #endif
41 #ifdef HAVE_NETINET_IN_H
42 #include <netinet/in.h>
43 #endif
44 #ifdef _XOPEN_SOURCE_EXTENDED
45 /* This define is "almost" required to build on HPUX 11 */
46 #include <arpa/inet.h>
47 #endif
48 #ifdef HAVE_NETDB_H
49 #include <netdb.h>
50 #endif
51 #ifdef HAVE_NETINET_TCP_H
52 #include <netinet/tcp.h> /* for TCP_NODELAY */
53 #endif
54
55 #define ENABLE_CURLX_PRINTF
56 /* make the curlx header define all printf() functions to use the curlx_*
57    versions instead */
58 #include "curlx.h" /* from the private lib dir */
59 #include "getpart.h"
60 #include "util.h"
61
62 /* include memdebug.h last */
63 #include "memdebug.h"
64
65 #if !defined(CURL_SWS_FORK_ENABLED) && defined(HAVE_FORK)
66 /*
67  * The normal sws build for the plain standard curl test suite has no use for
68  * fork(), but if you feel wild and crazy and want to setup some more exotic
69  * tests. Define this and run...
70  */
71 #define CURL_SWS_FORK_ENABLED
72 #endif
73
74 #define REQBUFSIZ 150000
75 #define REQBUFSIZ_TXT "149999"
76
77 static long prevtestno=-1;    /* previous test number we served */
78 static long prevpartno=-1;    /* previous part number we served */
79 static bool prevbounce=FALSE; /* instructs the server to increase the part
80                                  number for a test in case the identical
81                                  testno+partno request shows up again */
82
83 #define RCMD_NORMALREQ 0 /* default request, use the tests file normally */
84 #define RCMD_IDLE      1 /* told to sit idle */
85 #define RCMD_STREAM    2 /* told to stream */
86
87 struct httprequest {
88   char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */
89   int checkindex; /* where to start checking of the request */
90   int offset;     /* size of the incoming request */
91   long testno;     /* test number found in the request */
92   long partno;     /* part number found in the request */
93   bool open;      /* keep connection open info, as found in the request */
94   bool auth_req;  /* authentication required, don't wait for body unless
95                      there's an Authorization header */
96   bool auth;      /* Authorization header present in the incoming request */
97   size_t cl;      /* Content-Length of the incoming request */
98   bool digest;    /* Authorization digest header found */
99   bool ntlm;      /* Authorization ntlm header found */
100   int pipe;       /* if non-zero, expect this many requests to do a "piped"
101                      request/response */
102   int skip;       /* if non-zero, the server is instructed to not read this
103                      many bytes from a PUT/POST request. Ie the client sends N
104                      bytes said in Content-Length, but the server only reads N
105                      - skip bytes. */
106   int rcmd;       /* doing a special command, see defines above */
107   int prot_version; /* HTTP version * 10 */
108   bool pipelining; /* true if request is pipelined */
109 };
110
111 static int ProcessRequest(struct httprequest *req);
112 static void storerequest(char *reqbuf, ssize_t totalsize);
113
114 #define DEFAULT_PORT 8999
115
116 #ifndef DEFAULT_LOGFILE
117 #define DEFAULT_LOGFILE "log/sws.log"
118 #endif
119
120 const char *serverlogfile = DEFAULT_LOGFILE;
121
122 #define SWSVERSION "cURL test suite HTTP server/0.1"
123
124 #define REQUEST_DUMP  "log/server.input"
125 #define RESPONSE_DUMP "log/server.response"
126
127 /* very-big-path support */
128 #define MAXDOCNAMELEN 140000
129 #define MAXDOCNAMELEN_TXT "139999"
130
131 #define REQUEST_KEYWORD_SIZE 256
132 #define REQUEST_KEYWORD_SIZE_TXT "255"
133
134 #define CMD_AUTH_REQUIRED "auth_required"
135
136 /* 'idle' means that it will accept the request fine but never respond
137    any data. Just keep the connection alive. */
138 #define CMD_IDLE "idle"
139
140 /* 'stream' means to send a never-ending stream of data */
141 #define CMD_STREAM "stream"
142
143 #define END_OF_HEADERS "\r\n\r\n"
144
145 enum {
146   DOCNUMBER_NOTHING = -7,
147   DOCNUMBER_QUIT    = -6,
148   DOCNUMBER_BADCONNECT = -5,
149   DOCNUMBER_INTERNAL= -4,
150   DOCNUMBER_CONNECT = -3,
151   DOCNUMBER_WERULEZ = -2,
152   DOCNUMBER_404     = -1
153 };
154
155
156 /* sent as reply to a QUIT */
157 static const char *docquit =
158 "HTTP/1.1 200 Goodbye" END_OF_HEADERS;
159
160 /* sent as reply to a CONNECT */
161 static const char *docconnect =
162 "HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;
163
164 /* sent as reply to a "bad" CONNECT */
165 static const char *docbadconnect =
166 "HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS;
167
168 /* send back this on 404 file not found */
169 static const char *doc404 = "HTTP/1.1 404 Not Found\r\n"
170     "Server: " SWSVERSION "\r\n"
171     "Connection: close\r\n"
172     "Content-Type: text/html"
173     END_OF_HEADERS
174     "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
175     "<HTML><HEAD>\n"
176     "<TITLE>404 Not Found</TITLE>\n"
177     "</HEAD><BODY>\n"
178     "<H1>Not Found</H1>\n"
179     "The requested URL was not found on this server.\n"
180     "<P><HR><ADDRESS>" SWSVERSION "</ADDRESS>\n" "</BODY></HTML>\n";
181
182 #ifdef SIGPIPE
183 static volatile int sigpipe;  /* Why? It's not used */
184 #endif
185
186 #ifdef SIGPIPE
187 static void sigpipe_handler(int sig)
188 {
189   (void)sig; /* prevent warning */
190   sigpipe = 1;
191 }
192 #endif
193
194 static int ProcessRequest(struct httprequest *req)
195 {
196   char *line=&req->reqbuf[req->checkindex];
197   bool chunked = FALSE;
198   static char request[REQUEST_KEYWORD_SIZE];
199   static char doc[MAXDOCNAMELEN];
200   char logbuf[256];
201   int prot_major, prot_minor;
202   char *end;
203   int error;
204   end = strstr(line, END_OF_HEADERS);
205
206   logmsg("ProcessRequest() called");
207
208   /* try to figure out the request characteristics as soon as possible, but
209      only once! */
210   if((req->testno == DOCNUMBER_NOTHING) &&
211      sscanf(line,
212             "%" REQUEST_KEYWORD_SIZE_TXT"s %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
213             request,
214             doc,
215             &prot_major,
216             &prot_minor) == 4) {
217     char *ptr;
218
219     req->prot_version = prot_major*10 + prot_minor;
220
221     /* find the last slash */
222     ptr = strrchr(doc, '/');
223
224     /* get the number after it */
225     if(ptr) {
226       FILE *stream;
227       char *filename;
228
229       if((strlen(doc) + strlen(request)) < 200)
230         sprintf(logbuf, "Got request: %s %s HTTP/%d.%d",
231                 request, doc, prot_major, prot_minor);
232       else
233         sprintf(logbuf, "Got a *HUGE* request HTTP/%d.%d",
234                 prot_major, prot_minor);
235       logmsg("%s", logbuf);
236
237       if(!strncmp("/verifiedserver", ptr, 15)) {
238         logmsg("Are-we-friendly question received");
239         req->testno = DOCNUMBER_WERULEZ;
240         return 1; /* done */
241       }
242
243       if(!strncmp("/quit", ptr, 5)) {
244         logmsg("Request-to-quit received");
245         req->testno = DOCNUMBER_QUIT;
246         return 1; /* done */
247       }
248
249       ptr++; /* skip the slash */
250
251       /* skip all non-numericals following the slash */
252       while(*ptr && !ISDIGIT(*ptr))
253         ptr++;
254
255       req->testno = strtol(ptr, &ptr, 10);
256
257       if(req->testno > 10000) {
258         req->partno = req->testno % 10000;
259         req->testno /= 10000;
260       }
261       else
262         req->partno = 0;
263
264       sprintf(logbuf, "Requested test number %ld part %ld",
265               req->testno, req->partno);
266
267       logmsg("%s", logbuf);
268
269       filename = test2file(req->testno);
270
271       stream=fopen(filename, "rb");
272       if(!stream) {
273         error = ERRNO;
274         logmsg("fopen() failed with error: %d %s", error, strerror(error));
275         logmsg("Error opening file: %s", filename);
276         logmsg("Couldn't open test file %ld", req->testno);
277         req->open = FALSE; /* closes connection */
278         return 1; /* done */
279       }
280       else {
281         char *cmd = NULL;
282         size_t cmdsize = 0;
283         int num=0;
284
285         /* get the custom server control "commands" */
286         cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize);
287         fclose(stream);
288
289         if(cmdsize) {
290           logmsg("Found a reply-servercmd section!");
291
292           if(!strncmp(CMD_AUTH_REQUIRED, cmd, strlen(CMD_AUTH_REQUIRED))) {
293             logmsg("instructed to require authorization header");
294             req->auth_req = TRUE;
295           }
296           else if(!strncmp(CMD_IDLE, cmd, strlen(CMD_IDLE))) {
297             logmsg("instructed to idle");
298             req->rcmd = RCMD_IDLE;
299             req->open = TRUE;
300           }
301           else if(!strncmp(CMD_STREAM, cmd, strlen(CMD_STREAM))) {
302             logmsg("instructed to stream");
303             req->rcmd = RCMD_STREAM;
304           }
305           else if(1 == sscanf(cmd, "pipe: %d", &num)) {
306             logmsg("instructed to allow a pipe size %d", num);
307             req->pipe = num-1; /* decrease by one since we don't count the
308                                   first request in this number */
309           }
310           else if(1 == sscanf(cmd, "skip: %d", &num)) {
311             logmsg("instructed to skip this number of bytes %d", num);
312             req->skip = num;
313           }
314           else {
315             logmsg("funny instruction found: %s", cmd);
316           }
317           free(cmd);
318         }
319       }
320     }
321     else {
322       if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d",
323                 doc, &prot_major, &prot_minor) == 3) {
324         sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request",
325                 doc, prot_major, prot_minor);
326         logmsg("%s", logbuf);
327
328         if(req->prot_version == 10)
329           req->open = FALSE; /* HTTP 1.0 closes connection by default */
330
331         if(!strncmp(doc, "bad", 3))
332           /* if the host name starts with bad, we fake an error here */
333           req->testno = DOCNUMBER_BADCONNECT;
334         else if(!strncmp(doc, "test", 4)) {
335           /* if the host name starts with test, the port number used in the
336              CONNECT line will be used as test number! */
337           char *portp = strchr(doc, ':');
338           if(portp)
339             req->testno = atoi(portp+1);
340           else
341             req->testno = DOCNUMBER_CONNECT;
342         }
343         else
344           req->testno = DOCNUMBER_CONNECT;
345       }
346       else {
347         logmsg("Did not find test number in PATH");
348         req->testno = DOCNUMBER_404;
349       }
350     }
351   }
352
353   if(!end) {
354     /* we don't have a complete request yet! */
355     logmsg("ProcessRequest returned without a complete request");
356     return 0;
357   }
358   logmsg("ProcessRequest found a complete request");
359
360   if(req->pipe)
361     /* we do have a full set, advance the checkindex to after the end of the
362        headers, for the pipelining case mostly */
363     req->checkindex += (end - line) + strlen(END_OF_HEADERS);
364
365   /* **** Persistence ****
366    *
367    * If the request is a HTTP/1.0 one, we close the connection unconditionally
368    * when we're done.
369    *
370    * If the request is a HTTP/1.1 one, we MUST check for a "Connection:"
371    * header that might say "close". If it does, we close a connection when
372    * this request is processed. Otherwise, we keep the connection alive for X
373    * seconds.
374    */
375
376   do {
377     if((req->cl==0) && curlx_strnequal("Content-Length:", line, 15)) {
378       /* If we don't ignore content-length, we read it and we read the whole
379          request including the body before we return. If we've been told to
380          ignore the content-length, we will return as soon as all headers
381          have been received */
382       size_t cl = strtol(line+15, &line, 10);
383       req->cl = cl - req->skip;
384
385       logmsg("Found Content-Length: %zu in the request", cl);
386       if(req->skip)
387         logmsg("... but will abort after %zu bytes", req->cl);
388       break;
389     }
390     else if(curlx_strnequal("Transfer-Encoding: chunked", line,
391                             strlen("Transfer-Encoding: chunked"))) {
392       /* chunked data coming in */
393       chunked = TRUE;
394     }
395
396     if(chunked) {
397       if(strstr(req->reqbuf, "\r\n0\r\n\r\n"))
398         /* end of chunks reached */
399         return 1; /* done */
400       else
401         return 0; /* not done */
402     }
403
404     line = strchr(line, '\n');
405     if(line)
406       line++;
407   } while(line);
408
409   if(!req->auth && strstr(req->reqbuf, "Authorization:")) {
410     req->auth = TRUE; /* Authorization: header present! */
411     if(req->auth_req)
412       logmsg("Authorization header found, as required");
413   }
414
415   if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) {
416     /* If the client is passing this Digest-header, we set the part number
417        to 1000. Not only to spice up the complexity of this, but to make
418        Digest stuff to work in the test suite. */
419     req->partno += 1000;
420     req->digest = TRUE; /* header found */
421     logmsg("Received Digest request, sending back data %ld", req->partno);
422   }
423   else if(!req->ntlm &&
424           strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) {
425     /* If the client is passing this type-3 NTLM header */
426     req->partno += 1002;
427     req->ntlm = TRUE; /* NTLM found */
428     logmsg("Received NTLM type-3, sending back data %ld", req->partno);
429     if(req->cl) {
430       logmsg("  Expecting %zu POSTed bytes", req->cl);
431     }
432   }
433   else if(!req->ntlm &&
434           strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) {
435     /* If the client is passing this type-1 NTLM header */
436     req->partno += 1001;
437     req->ntlm = TRUE; /* NTLM found */
438     logmsg("Received NTLM type-1, sending back data %ld", req->partno);
439   }
440   if(strstr(req->reqbuf, "Connection: close"))
441     req->open = FALSE; /* close connection after this request */
442
443   if(!req->pipe &&
444      req->open &&
445      req->prot_version >= 11 &&
446      end &&
447      req->reqbuf + req->offset > end + strlen(END_OF_HEADERS) &&
448      (!strncmp(req->reqbuf, "GET", strlen("GET")) ||
449       !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) {
450     /* If we have a persistent connection, HTTP version >= 1.1
451        and GET/HEAD request, enable pipelining. */
452     req->checkindex = (end - req->reqbuf) + strlen(END_OF_HEADERS);
453     req->pipelining = TRUE;
454   }
455
456   while(req->pipe) {
457     /* scan for more header ends within this chunk */
458     line = &req->reqbuf[req->checkindex];
459     end = strstr(line, END_OF_HEADERS);
460     if(!end)
461       break;
462     req->checkindex += (end - line) + strlen(END_OF_HEADERS);
463     req->pipe--;
464   }
465
466
467   /* If authentication is required and no auth was provided, end now. This
468      makes the server NOT wait for PUT/POST data and you can then make the
469      test case send a rejection before any such data has been sent. Test case
470      154 uses this.*/
471   if(req->auth_req && !req->auth)
472     return 1;
473
474   if(req->cl > 0) {
475     if(req->cl <= req->offset - (end - req->reqbuf) - strlen(END_OF_HEADERS))
476       return 1; /* done */
477     else
478       return 0; /* not complete yet */
479   }
480
481   return 1; /* done */
482 }
483
484 /* store the entire request in a file */
485 static void storerequest(char *reqbuf, ssize_t totalsize)
486 {
487   int res;
488   int error;
489   ssize_t written;
490   ssize_t writeleft;
491   FILE *dump;
492
493   if (reqbuf == NULL)
494     return;
495
496   if (totalsize == 0)
497     return;
498   else if (totalsize < 0) {
499     logmsg("Invalid size (%zd bytes) for request input. Not written to %s",
500            totalsize, REQUEST_DUMP);
501     return;
502   }
503
504   do {
505     dump = fopen(REQUEST_DUMP, "ab");
506   } while ((dump == NULL) && ((error = ERRNO) == EINTR));
507   if (dump == NULL) {
508     logmsg("Error opening file %s error: %d %s",
509            REQUEST_DUMP, error, strerror(error));
510     logmsg("Failed to write request input to " REQUEST_DUMP);
511     return;
512   }
513
514   writeleft = totalsize;
515   do {
516     written = (ssize_t)fwrite((void *) &reqbuf[totalsize-writeleft],
517                               1, (size_t)writeleft, dump);
518     if (written > 0)
519       writeleft -= written;
520   } while ((writeleft > 0) && ((error = ERRNO) == EINTR));
521
522   if (writeleft > 0) {
523     logmsg("Error writing file %s error: %d %s",
524            REQUEST_DUMP, error, strerror(error));
525     logmsg("Wrote only (%zd bytes) of (%zd bytes) request input to %s",
526            totalsize-writeleft, totalsize, REQUEST_DUMP);
527   }
528
529   do {
530     res = fclose(dump);
531   } while(res && ((error = ERRNO) == EINTR));
532   if(res)
533     logmsg("Error closing file %s error: %d %s",
534            REQUEST_DUMP, error, strerror(error));
535
536   if(!writeleft)
537     logmsg("Wrote request (%zd bytes) input to " REQUEST_DUMP,
538            totalsize);
539 }
540
541 /* return 0 on success, non-zero on failure */
542 static int get_request(curl_socket_t sock, struct httprequest *req)
543 {
544   int fail = 0;
545   char *reqbuf = req->reqbuf;
546   ssize_t got = 0;
547
548   char *pipereq;
549   int pipereq_length = 0;
550
551   if(req->pipelining) {
552     pipereq = reqbuf + req->checkindex;
553     pipereq_length = req->offset - req->checkindex;
554   }
555
556   /*** Init the httprequest structure properly for the upcoming request ***/
557
558   req->checkindex = 0;
559   req->offset = 0;
560   req->testno = DOCNUMBER_NOTHING;
561   req->partno = 0;
562   req->open = TRUE;
563   req->auth_req = FALSE;
564   req->auth = FALSE;
565   req->cl = 0;
566   req->digest = FALSE;
567   req->ntlm = FALSE;
568   req->pipe = 0;
569   req->skip = 0;
570   req->rcmd = RCMD_NORMALREQ;
571   req->prot_version = 0;
572   req->pipelining = FALSE;
573
574   /*** end of httprequest init ***/
575
576   while (req->offset < REQBUFSIZ-1) {
577     if(pipereq_length) {
578       memmove(reqbuf, pipereq, pipereq_length);
579       got = pipereq_length;
580       pipereq_length = 0;
581     }
582     else {
583       if(req->skip)
584         /* we are instructed to not read the entire thing, so we make sure to only
585            read what we're supposed to and NOT read the enire thing the client
586            wants to send! */
587         got = sread(sock, reqbuf + req->offset, req->cl);
588       else
589         got = sread(sock, reqbuf + req->offset, REQBUFSIZ-1 - req->offset);
590     }
591     if (got <= 0) {
592       if (got < 0) {
593         logmsg("recv() returned error: %d", SOCKERRNO);
594         return DOCNUMBER_INTERNAL;
595       }
596       logmsg("Connection closed by client");
597       reqbuf[req->offset] = '\0';
598
599       /* dump the request receivied so far to the external file */
600       storerequest(reqbuf, req->offset);
601       return DOCNUMBER_INTERNAL;
602     }
603
604     logmsg("Read %zd bytes", got);
605
606     req->offset += got;
607     reqbuf[req->offset] = '\0';
608
609     if(ProcessRequest(req)) {
610       if(req->pipe--) {
611         logmsg("Waiting for another piped request");
612         continue;
613       }
614       break;
615     }
616   }
617
618   if((req->offset == REQBUFSIZ-1) && (got > 0)) {
619     logmsg("Request would overflow buffer, closing connection");
620     /* dump request received so far to external file anyway */
621     reqbuf[REQBUFSIZ-1] = '\0';
622     fail = 1;
623   }
624   else if(req->offset > REQBUFSIZ-1) {
625     logmsg("Request buffer overflow, closing connection");
626     /* dump request received so far to external file anyway */
627     reqbuf[REQBUFSIZ-1] = '\0';
628     fail = 1;
629   }
630   else
631     reqbuf[req->offset] = '\0';
632
633   /* dump the request to an external file */
634   storerequest(reqbuf, req->pipelining ? req->checkindex : req->offset);
635
636   return fail; /* return 0 on success */
637 }
638
639 /* returns -1 on failure */
640 static int send_doc(curl_socket_t sock, struct httprequest *req)
641 {
642   ssize_t written;
643   size_t count;
644   const char *buffer;
645   char *ptr=NULL;
646   FILE *stream;
647   char *cmd=NULL;
648   size_t cmdsize=0;
649   FILE *dump;
650   bool persistant = TRUE;
651   bool sendfailure = FALSE;
652   size_t responsesize;
653   int error;
654   int res;
655
656   static char weare[256];
657
658   char partbuf[80]="data";
659
660   logmsg("Send response number %ld part %ld", req->testno, req->partno);
661
662   switch(req->rcmd) {
663   default:
664   case RCMD_NORMALREQ:
665     break; /* continue with business as usual */
666   case RCMD_STREAM:
667 #define STREAMTHIS "a string to stream 01234567890\n"
668     count = strlen(STREAMTHIS);
669     while(1) {
670       written = swrite(sock, STREAMTHIS, count);
671       if(written != (ssize_t)count) {
672         logmsg("Stopped streaming");
673         break;
674       }
675     }
676     return -1;
677   case RCMD_IDLE:
678     /* Do nothing. Sit idle. Pretend it rains. */
679     return 0;
680   }
681
682   req->open = FALSE;
683
684   if(req->testno < 0) {
685     size_t msglen;
686     char msgbuf[64];
687
688     switch(req->testno) {
689     case DOCNUMBER_QUIT:
690       logmsg("Replying to QUIT");
691       buffer = docquit;
692       break;
693     case DOCNUMBER_WERULEZ:
694       /* we got a "friends?" question, reply back that we sure are */
695       logmsg("Identifying ourselves as friends");
696       sprintf(msgbuf, "WE ROOLZ: %ld\r\n", (long)getpid());
697       msglen = strlen(msgbuf);
698       sprintf(weare, "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s",
699               msglen, msgbuf);
700       buffer = weare;
701       break;
702     case DOCNUMBER_INTERNAL:
703       logmsg("Bailing out due to internal error");
704       return -1;
705     case DOCNUMBER_CONNECT:
706       logmsg("Replying to CONNECT");
707       buffer = docconnect;
708       break;
709     case DOCNUMBER_BADCONNECT:
710       logmsg("Replying to a bad CONNECT");
711       buffer = docbadconnect;
712       break;
713     case DOCNUMBER_404:
714     default:
715       logmsg("Replying to with a 404");
716       buffer = doc404;
717       break;
718     }
719     ptr = NULL;
720     stream=NULL;
721
722     count = strlen(buffer);
723   }
724   else {
725     char *filename = test2file(req->testno);
726
727     if(0 != req->partno)
728       sprintf(partbuf, "data%ld", req->partno);
729
730     stream=fopen(filename, "rb");
731     if(!stream) {
732       error = ERRNO;
733       logmsg("fopen() failed with error: %d %s", error, strerror(error));
734       logmsg("Error opening file: %s", filename);
735       logmsg("Couldn't open test file");
736       return 0;
737     }
738     else {
739       buffer = spitout(stream, "reply", partbuf, &count);
740       ptr = (char *)buffer;
741       fclose(stream);
742     }
743
744     /* re-open the same file again */
745     stream=fopen(filename, "rb");
746     if(!stream) {
747       error = ERRNO;
748       logmsg("fopen() failed with error: %d %s", error, strerror(error));
749       logmsg("Error opening file: %s", filename);
750       logmsg("Couldn't open test file");
751       return 0;
752     }
753     else {
754       /* get the custom server control "commands" */
755       cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize);
756       fclose(stream);
757     }
758   }
759
760   dump = fopen(RESPONSE_DUMP, "ab"); /* b is for windows-preparing */
761   if(!dump) {
762     error = ERRNO;
763     logmsg("fopen() failed with error: %d %s", error, strerror(error));
764     logmsg("Error opening file: %s", RESPONSE_DUMP);
765     logmsg("couldn't create logfile: " RESPONSE_DUMP);
766     return -1;
767   }
768
769   /* If the word 'swsclose' is present anywhere in the reply chunk, the
770      connection will be closed after the data has been sent to the requesting
771      client... */
772   if(strstr(buffer, "swsclose") || !count) {
773     persistant = FALSE;
774     logmsg("connection close instruction \"swsclose\" found in response");
775   }
776   if(strstr(buffer, "swsbounce")) {
777     prevbounce = TRUE;
778     logmsg("enable \"swsbounce\" in the next request");
779   }
780   else
781     prevbounce = FALSE;
782
783
784   responsesize = count;
785   do {
786     /* Ok, we send no more than 200 bytes at a time, just to make sure that
787        larger chunks are split up so that the client will need to do multiple
788        recv() calls to get it and thus we exercise that code better */
789     size_t num = count;
790     if(num > 200)
791       num = 200;
792     written = swrite(sock, buffer, num);
793     if (written < 0) {
794       sendfailure = TRUE;
795       break;
796     }
797     else {
798       logmsg("Sent off %zd bytes", written);
799     }
800     /* write to file as well */
801     fwrite(buffer, 1, written, dump);
802
803     count -= written;
804     buffer += written;
805   } while(count>0);
806
807   do {
808     res = fclose(dump);
809   } while(res && ((error = ERRNO) == EINTR));
810   if(res)
811     logmsg("Error closing file %s error: %d %s",
812            RESPONSE_DUMP, error, strerror(error));
813
814   if(sendfailure) {
815     logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent",
816            responsesize-count, responsesize);
817     if(ptr)
818       free(ptr);
819     if(cmd)
820       free(cmd);
821     return -1;
822   }
823
824   logmsg("Response sent (%zu bytes) and written to " RESPONSE_DUMP,
825          responsesize);
826
827   if(ptr)
828     free(ptr);
829
830   if(cmdsize > 0 ) {
831     char command[32];
832     int num;
833     ptr=cmd;
834     do {
835       if(2 == sscanf(ptr, "%31s %d", command, &num)) {
836         if(!strcmp("wait", command)) {
837           logmsg("Told to sleep for %d seconds", num);
838           sleep(num); /* wait this many seconds */
839         }
840         else
841           logmsg("Unknown command in reply command section");
842       }
843       ptr = strchr(ptr, '\n');
844       if(ptr)
845         ptr++;
846       else
847         ptr = NULL;
848     } while(ptr && *ptr);
849   }
850   if(cmd)
851     free(cmd);
852
853   req->open = persistant;
854
855   prevtestno = req->testno;
856   prevpartno = req->partno;
857
858   return 0;
859 }
860
861 static bool use_ipv6=FALSE;
862
863 int main(int argc, char *argv[])
864 {
865   struct sockaddr_in me;
866 #ifdef ENABLE_IPV6
867   struct sockaddr_in6 me6;
868 #endif /* ENABLE_IPV6 */
869   curl_socket_t sock, msgsock;
870   int flag;
871   unsigned short port = DEFAULT_PORT;
872   char *pidname= (char *)".http.pid";
873   struct httprequest req;
874   int rc;
875   int arg=1;
876 #ifdef CURL_SWS_FORK_ENABLED
877   bool use_fork = FALSE;
878 #endif
879
880   while(argc>arg) {
881     if(!strcmp("--version", argv[arg])) {
882       printf("sws IPv4%s"
883 #ifdef CURL_SWS_FORK_ENABLED
884              " FORK"
885 #endif
886              "\n"
887              ,
888 #ifdef ENABLE_IPV6
889              "/IPv6"
890 #else
891              ""
892 #endif
893              );
894       return 0;
895     }
896     else if(!strcmp("--pidfile", argv[arg])) {
897       arg++;
898       if(argc>arg)
899         pidname = argv[arg++];
900     }
901     else if(!strcmp("--ipv6", argv[arg])) {
902 #ifdef ENABLE_IPV6
903       use_ipv6=TRUE;
904 #endif
905       arg++;
906     }
907 #ifdef CURL_SWS_FORK_ENABLED
908     else if(!strcmp("--fork", argv[arg])) {
909       use_fork=TRUE;
910       arg++;
911     }
912 #endif
913     else if(argc>arg) {
914
915       if(atoi(argv[arg]))
916         port = (unsigned short)atoi(argv[arg++]);
917
918       if(argc>arg)
919         path = argv[arg++];
920     }
921   }
922
923 #ifdef WIN32
924   win32_init();
925   atexit(win32_cleanup);
926 #else
927
928 #ifdef SIGPIPE
929 #ifdef HAVE_SIGNAL
930   signal(SIGPIPE, sigpipe_handler);
931 #endif
932 #ifdef HAVE_SIGINTERRUPT
933   siginterrupt(SIGPIPE, 1);
934 #endif
935 #endif
936 #endif
937
938 #ifdef ENABLE_IPV6
939   if(!use_ipv6)
940 #endif
941     sock = socket(AF_INET, SOCK_STREAM, 0);
942 #ifdef ENABLE_IPV6
943   else
944     sock = socket(AF_INET6, SOCK_STREAM, 0);
945 #endif
946
947   if (CURL_SOCKET_BAD == sock) {
948     logmsg("Error opening socket: %d", SOCKERRNO);
949     return 1;
950   }
951
952   flag = 1;
953   if (0 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
954             (void *) &flag, sizeof(flag))) {
955     logmsg("setsockopt(SO_REUSEADDR) failed: %d", SOCKERRNO);
956     sclose(sock);
957     return 1;
958   }
959
960 #ifdef ENABLE_IPV6
961   if(!use_ipv6) {
962 #endif
963     memset(&me, 0, sizeof(me));
964     me.sin_family = AF_INET;
965     me.sin_addr.s_addr = INADDR_ANY;
966     me.sin_port = htons(port);
967     rc = bind(sock, (struct sockaddr *) &me, sizeof(me));
968 #ifdef ENABLE_IPV6
969   }
970   else {
971     memset(&me6, 0, sizeof(me6));
972     me6.sin6_family = AF_INET6;
973     me6.sin6_addr = in6addr_any;
974     me6.sin6_port = htons(port);
975     rc = bind(sock, (struct sockaddr *) &me6, sizeof(me6));
976   }
977 #endif /* ENABLE_IPV6 */
978   if(0 != rc) {
979     logmsg("Error binding socket: %d", SOCKERRNO);
980     sclose(sock);
981     return 1;
982   }
983
984   if(!write_pidfile(pidname)) {
985     sclose(sock);
986     return 1;
987   }
988
989   logmsg("Running IPv%d version on port %hu",
990          (use_ipv6?6:4), port);
991
992   /* start accepting connections */
993   rc = listen(sock, 5);
994   if(0 != rc) {
995     logmsg("listen() failed with error: %d", SOCKERRNO);
996     sclose(sock);
997     return 1;
998   }
999
1000   while (1) {
1001     msgsock = accept(sock, NULL, NULL);
1002
1003     if (CURL_SOCKET_BAD == msgsock) {
1004       printf("MAJOR ERROR: accept() failed with error: %d\n", SOCKERRNO);
1005       break;
1006     }
1007
1008     set_advisor_read_lock(SERVERLOGS_LOCK);
1009
1010 #ifdef CURL_SWS_FORK_ENABLED
1011     if(use_fork) {
1012       /* The fork enabled version just forks off the child and don't care
1013          about it anymore, so don't assume otherwise. Beware and don't do
1014          this at home. */
1015       rc = fork();
1016       if(-1 == rc) {
1017         printf("MAJOR ERROR: fork() failed!\n");
1018         break;
1019       }
1020     }
1021     else
1022       /* not a fork, just set rc so the following proceeds nicely */
1023       rc = 0;
1024     /* 0 is returned to the child */
1025     if(0 == rc) {
1026 #endif
1027     logmsg("====> Client connect");
1028
1029 #ifdef TCP_NODELAY
1030     /*
1031      * Disable the Nagle algorithm to make it easier to send out a large
1032      * response in many small segments to torture the clients more.
1033      */
1034     flag = 1;
1035     if (setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
1036                    (void *)&flag, sizeof(flag)) == -1) {
1037       logmsg("====> TCP_NODELAY failed");
1038     }
1039 #endif
1040
1041     /* initialization of httprequest struct is done in get_request(), but due
1042        to pipelining treatment the pipelining struct field must be initialized
1043        previously to FALSE every time a new connection arrives. */
1044
1045     req.pipelining = FALSE;
1046
1047     do {
1048       if(get_request(msgsock, &req))
1049         /* non-zero means error, break out of loop */
1050         break;
1051
1052       if(prevbounce) {
1053         /* bounce treatment requested */
1054         if((req.testno == prevtestno) &&
1055            (req.partno == prevpartno)) {
1056           req.partno++;
1057           logmsg("BOUNCE part number to %ld", req.partno);
1058         }
1059         else {
1060           prevbounce = FALSE;
1061           prevtestno = -1;
1062           prevpartno = -1;
1063         }
1064       }
1065
1066       send_doc(msgsock, &req);
1067
1068       if((req.testno < 0) && (req.testno != DOCNUMBER_CONNECT)) {
1069         logmsg("special request received, no persistency");
1070         break;
1071       }
1072       if(!req.open) {
1073         logmsg("instructed to close connection after server-reply");
1074         break;
1075       }
1076
1077       if(req.open)
1078         logmsg("=> persistant connection request ended, awaits new request");
1079       /* if we got a CONNECT, loop and get another request as well! */
1080     } while(req.open || (req.testno == DOCNUMBER_CONNECT));
1081
1082     logmsg("====> Client disconnect");
1083     sclose(msgsock);
1084
1085     clear_advisor_read_lock(SERVERLOGS_LOCK);
1086
1087     if (req.testno == DOCNUMBER_QUIT)
1088       break;
1089 #ifdef CURL_SWS_FORK_ENABLED
1090     }
1091 #endif
1092   }
1093
1094   sclose(sock);
1095
1096   clear_advisor_read_lock(SERVERLOGS_LOCK);
1097
1098   return 0;
1099 }
1100