📄 sws.c
字号:
/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: sws.c,v 1.110 2008-01-25 05:08:53 yangtse Exp $ ***************************************************************************//* sws.c: simple (silly?) web server This code was originally graciously donated to the project by Juergen Wilke. Thanks a bunch! */#include "setup.h" /* portability help from the lib directory */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdarg.h>#include <signal.h>#include <time.h>#include <sys/time.h>#include <sys/types.h>#include <ctype.h>#ifdef HAVE_UNISTD_H#include <unistd.h>#endif#ifdef HAVE_SYS_SOCKET_H#include <sys/socket.h>#endif#ifdef HAVE_NETINET_IN_H#include <netinet/in.h>#endif#ifdef _XOPEN_SOURCE_EXTENDED/* This define is "almost" required to build on HPUX 11 */#include <arpa/inet.h>#endif#ifdef HAVE_NETDB_H#include <netdb.h>#endif#ifdef HAVE_NETINET_TCP_H#include <netinet/tcp.h> /* for TCP_NODELAY */#endif#define ENABLE_CURLX_PRINTF/* make the curlx header define all printf() functions to use the curlx_* versions instead */#include "curlx.h" /* from the private lib dir */#include "getpart.h"#include "util.h"/* include memdebug.h last */#include "memdebug.h"#if !defined(CURL_SWS_FORK_ENABLED) && defined(HAVE_FORK)/* * The normal sws build for the plain standard curl test suite has no use for * fork(), but if you feel wild and crazy and want to setup some more exotic * tests. Define this and run... */#define CURL_SWS_FORK_ENABLED#endif#define REQBUFSIZ 150000#define REQBUFSIZ_TXT "149999"long prevtestno=-1; /* previous test number we served */long prevpartno=-1; /* previous part number we served */bool prevbounce; /* instructs the server to increase the part number for a test in case the identical testno+partno request shows up again */#define RCMD_NORMALREQ 0 /* default request, use the tests file normally */#define RCMD_IDLE 1 /* told to sit idle */#define RCMD_STREAM 2 /* told to stream */struct httprequest { char reqbuf[REQBUFSIZ]; /* buffer area for the incoming request */ int checkindex; /* where to start checking of the request */ int offset; /* size of the incoming request */ long testno; /* test number found in the request */ long partno; /* part number found in the request */ int open; /* keep connection open info, as found in the request */ bool auth_req; /* authentication required, don't wait for body unless there's an Authorization header */ bool auth; /* Authorization header present in the incoming request */ size_t cl; /* Content-Length of the incoming request */ bool digest; /* Authorization digest header found */ bool ntlm; /* Authorization ntlm header found */ int pipe; /* if non-zero, expect this many requests to do a "piped" request/response */ int rcmd; /* doing a special command, see defines above */ int prot_version; /* HTTP version * 10 */ bool pipelining; /* true if request is pipelined */};int ProcessRequest(struct httprequest *req);void storerequest(char *reqbuf, ssize_t totalsize);#define DEFAULT_PORT 8999#ifndef DEFAULT_LOGFILE#define DEFAULT_LOGFILE "log/sws.log"#endifconst char *serverlogfile = DEFAULT_LOGFILE;#define SWSVERSION "cURL test suite HTTP server/0.1"#define REQUEST_DUMP "log/server.input"#define RESPONSE_DUMP "log/server.response"/* very-big-path support */#define MAXDOCNAMELEN 140000#define MAXDOCNAMELEN_TXT "139999"#define REQUEST_KEYWORD_SIZE 256#define CMD_AUTH_REQUIRED "auth_required"/* 'idle' means that it will accept the request fine but never respond any data. Just keep the connection alive. */#define CMD_IDLE "idle"/* 'stream' means to send a never-ending stream of data */#define CMD_STREAM "stream"#define END_OF_HEADERS "\r\n\r\n"enum { DOCNUMBER_NOTHING = -7, DOCNUMBER_QUIT = -6, DOCNUMBER_BADCONNECT = -5, DOCNUMBER_INTERNAL= -4, DOCNUMBER_CONNECT = -3, DOCNUMBER_WERULEZ = -2, DOCNUMBER_404 = -1};/* sent as reply to a QUIT */static const char *docquit ="HTTP/1.1 200 Goodbye" END_OF_HEADERS;/* sent as reply to a CONNECT */static const char *docconnect ="HTTP/1.1 200 Mighty fine indeed" END_OF_HEADERS;/* sent as reply to a "bad" CONNECT */static const char *docbadconnect ="HTTP/1.1 501 Forbidden you fool" END_OF_HEADERS;/* send back this on 404 file not found */static const char *doc404 = "HTTP/1.1 404 Not Found\r\n" "Server: " SWSVERSION "\r\n" "Connection: close\r\n" "Content-Type: text/html" END_OF_HEADERS "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" "<HTML><HEAD>\n" "<TITLE>404 Not Found</TITLE>\n" "</HEAD><BODY>\n" "<H1>Not Found</H1>\n" "The requested URL was not found on this server.\n" "<P><HR><ADDRESS>" SWSVERSION "</ADDRESS>\n" "</BODY></HTML>\n";#ifdef SIGPIPEstatic volatile int sigpipe; /* Why? It's not used */#endif#ifdef SIGPIPEstatic void sigpipe_handler(int sig){ (void)sig; /* prevent warning */ sigpipe = 1;}#endifint ProcessRequest(struct httprequest *req){ char *line=&req->reqbuf[req->checkindex]; char chunked=FALSE; static char request[REQUEST_KEYWORD_SIZE]; static char doc[MAXDOCNAMELEN]; char logbuf[256]; int prot_major, prot_minor; char *end; int error; end = strstr(line, END_OF_HEADERS); logmsg("ProcessRequest() called"); /* try to figure out the request characteristics as soon as possible, but only once! */ if((req->testno == DOCNUMBER_NOTHING) && sscanf(line, "%" REQBUFSIZ_TXT"s %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d", request, doc, &prot_major, &prot_minor) == 4) { char *ptr; req->prot_version = prot_major*10 + prot_minor; /* find the last slash */ ptr = strrchr(doc, '/'); /* get the number after it */ if(ptr) { FILE *stream; char *filename; if((strlen(doc) + strlen(request)) < 200) sprintf(logbuf, "Got request: %s %s HTTP/%d.%d", request, doc, prot_major, prot_minor); else sprintf(logbuf, "Got a *HUGE* request HTTP/%d.%d", prot_major, prot_minor); logmsg("%s", logbuf); if(!strncmp("/verifiedserver", ptr, 15)) { logmsg("Are-we-friendly question received"); req->testno = DOCNUMBER_WERULEZ; return 1; /* done */ } if(!strncmp("/quit", ptr, 5)) { logmsg("Request-to-quit received"); req->testno = DOCNUMBER_QUIT; return 1; /* done */ } ptr++; /* skip the slash */ /* skip all non-numericals following the slash */ while(*ptr && !ISDIGIT(*ptr)) ptr++; req->testno = strtol(ptr, &ptr, 10); if(req->testno > 10000) { req->partno = req->testno % 10000; req->testno /= 10000; } else req->partno = 0; sprintf(logbuf, "Requested test number %ld part %ld", req->testno, req->partno); logmsg("%s", logbuf); filename = test2file(req->testno); stream=fopen(filename, "rb"); if(!stream) { error = ERRNO; logmsg("fopen() failed with error: %d %s", error, strerror(error)); logmsg("Error opening file: %s", filename); logmsg("Couldn't open test file %d", req->testno); req->open = FALSE; /* closes connection */ return 1; /* done */ } else { char *cmd = NULL; size_t cmdsize = 0; int num=0; /* get the custom server control "commands" */ cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize); fclose(stream); if(cmdsize) { logmsg("Found a reply-servercmd section!"); if(!strncmp(CMD_AUTH_REQUIRED, cmd, strlen(CMD_AUTH_REQUIRED))) { logmsg("instructed to require authorization header"); req->auth_req = TRUE; } else if(!strncmp(CMD_IDLE, cmd, strlen(CMD_IDLE))) { logmsg("instructed to idle"); req->rcmd = RCMD_IDLE; req->open = TRUE; } else if(!strncmp(CMD_STREAM, cmd, strlen(CMD_STREAM))) { logmsg("instructed to stream"); req->rcmd = RCMD_STREAM; } else if(1 == sscanf(cmd, "pipe: %d", &num)) { logmsg("instructed to allow a pipe size %d", num); req->pipe = num-1; /* decrease by one since we don't count the first request in this number */ } free(cmd); } } } else { if(sscanf(req->reqbuf, "CONNECT %" MAXDOCNAMELEN_TXT "s HTTP/%d.%d", doc, &prot_major, &prot_minor) == 3) { sprintf(logbuf, "Received a CONNECT %s HTTP/%d.%d request", doc, prot_major, prot_minor); logmsg("%s", logbuf); if(req->prot_version == 10) req->open = FALSE; /* HTTP 1.0 closes connection by default */ if(!strncmp(doc, "bad", 3)) /* if the host name starts with bad, we fake an error here */ req->testno = DOCNUMBER_BADCONNECT; else if(!strncmp(doc, "test", 4)) { /* if the host name starts with test, the port number used in the CONNECT line will be used as test number! */ char *portp = strchr(doc, ':'); if(portp) req->testno = atoi(portp+1); else req->testno = DOCNUMBER_CONNECT; } else req->testno = DOCNUMBER_CONNECT; } else { logmsg("Did not find test number in PATH"); req->testno = DOCNUMBER_404; } } } if(!end) { /* we don't have a complete request yet! */ logmsg("ProcessRequest returned without a complete request"); return 0; } logmsg("ProcessRequest found a complete request"); if(req->pipe) /* we do have a full set, advance the checkindex to after the end of the headers, for the pipelining case mostly */ req->checkindex += (end - line) + strlen(END_OF_HEADERS); /* **** Persistancy **** * * If the request is a HTTP/1.0 one, we close the connection unconditionally * when we're done. * * If the request is a HTTP/1.1 one, we MUST check for a "Connection:" * header that might say "close". If it does, we close a connection when * this request is processed. Otherwise, we keep the connection alive for X * seconds. */ do { if(!req->cl && curlx_strnequal("Content-Length:", line, 15)) { /* If we don't ignore content-length, we read it and we read the whole request including the body before we return. If we've been told to ignore the content-length, we will return as soon as all headers have been received */ req->cl = strtol(line+15, &line, 10); logmsg("Found Content-Length: %d in the request", req->cl); break; } else if(curlx_strnequal("Transfer-Encoding: chunked", line, strlen("Transfer-Encoding: chunked"))) { /* chunked data coming in */ chunked = TRUE; } if(chunked) { if(strstr(req->reqbuf, "\r\n0\r\n\r\n")) /* end of chunks reached */ return 1; /* done */ else return 0; /* not done */ } line = strchr(line, '\n'); if(line) line++; } while(line); if(!req->auth && strstr(req->reqbuf, "Authorization:")) { req->auth = TRUE; /* Authorization: header present! */ if(req->auth_req) logmsg("Authorization header found, as required"); } if(!req->digest && strstr(req->reqbuf, "Authorization: Digest")) { /* If the client is passing this Digest-header, we set the part number to 1000. Not only to spice up the complexity of this, but to make Digest stuff to work in the test suite. */ req->partno += 1000; req->digest = TRUE; /* header found */ logmsg("Received Digest request, sending back data %d", req->partno); } else if(!req->ntlm && strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAD")) { /* If the client is passing this type-3 NTLM header */ req->partno += 1002; req->ntlm = TRUE; /* NTLM found */ logmsg("Received NTLM type-3, sending back data %d", req->partno); if(req->cl) { logmsg(" Expecting %d POSTed bytes", req->cl); } } else if(!req->ntlm && strstr(req->reqbuf, "Authorization: NTLM TlRMTVNTUAAB")) { /* If the client is passing this type-1 NTLM header */ req->partno += 1001; req->ntlm = TRUE; /* NTLM found */ logmsg("Received NTLM type-1, sending back data %d", req->partno); } if(strstr(req->reqbuf, "Connection: close")) req->open = FALSE; /* close connection after this request */ if(!req->pipe && req->open && req->prot_version >= 11 && end && req->reqbuf + req->offset > end + strlen(END_OF_HEADERS) && (!strncmp(req->reqbuf, "GET", strlen("GET")) || !strncmp(req->reqbuf, "HEAD", strlen("HEAD")))) { /* If we have a persistent connection, HTTP version >= 1.1 and GET/HEAD request, enable pipelining. */ req->checkindex = (end - req->reqbuf) + strlen(END_OF_HEADERS); req->pipelining = TRUE; } while(req->pipe) { /* scan for more header ends within this chunk */ line = &req->reqbuf[req->checkindex]; end = strstr(line, END_OF_HEADERS); if(!end) break; req->checkindex += (end - line) + strlen(END_OF_HEADERS); req->pipe--; } /* If authentication is required and no auth was provided, end now. This makes the server NOT wait for PUT/POST data and you can then make the test case send a rejection before any such data has been sent. Test case 154 uses this.*/ if(req->auth_req && !req->auth) return 1; if(req->cl) { if(req->cl <= req->offset - (end - req->reqbuf) - strlen(END_OF_HEADERS)) return 1; /* done */ else return 0; /* not complete yet */ } return 1; /* done */}/* store the entire request in a file */void storerequest(char *reqbuf, ssize_t totalsize){ int error; ssize_t written; ssize_t writeleft; FILE *dump; if (reqbuf == NULL) return; if (totalsize == 0) return; else if (totalsize < 0) { logmsg("Invalid size (%d bytes) for request input. Not written to %s", totalsize, REQUEST_DUMP); return; } do { dump = fopen(REQUEST_DUMP, "ab"); } while ((dump == NULL) && ((error = ERRNO) == EINTR)); if (dump == NULL) { logmsg("Error opening file %s error: %d %s", REQUEST_DUMP, error, strerror(error)); logmsg("Failed to write request input to " REQUEST_DUMP); return; } writeleft = totalsize; do { written = (ssize_t)fwrite((void *) &reqbuf[totalsize-writeleft], 1, (size_t)writeleft, dump); if (written > 0) writeleft -= written; } while ((writeleft > 0) && ((error = ERRNO) == EINTR)); fclose(dump); /* close it ASAP */ if (writeleft > 0) { logmsg("Error writing file %s error: %d %s", REQUEST_DUMP, error, strerror(error)); logmsg("Wrote only (%d bytes) of (%d bytes) request input to %s", totalsize-writeleft, totalsize, REQUEST_DUMP); } else { logmsg("Wrote request (%d bytes) input to " REQUEST_DUMP, totalsize); }}/* return 0 on success, non-zero on failure */static int get_request(curl_socket_t sock, struct httprequest *req){ int fail= FALSE; char *reqbuf = req->reqbuf;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -