1 /* Copyright information is at the end of the file */
11 #include "xmlrpc_config.h"
12 #include "mallocvar.h"
13 #include "xmlrpc-c/string_int.h"
14 #include "xmlrpc-c/abyss.h"
22 #include "abyss_info.h"
26 /*********************************************************************
28 *********************************************************************/
30 /*********************************************************************
32 *********************************************************************/
35 initRequestInfo(TRequestInfo * const requestInfoP,
36 httpVersion const httpVersion,
37 const char * const requestLine,
38 TMethod const httpMethod,
39 const char * const host,
40 unsigned int const port,
41 const char * const path,
42 const char * const query) {
43 /*----------------------------------------------------------------------------
44 Set up the request info structure. For information that is
45 controlled by headers, use the defaults -- I.e. the value that
46 applies if the request contains no applicable header.
47 -----------------------------------------------------------------------------*/
48 requestInfoP->requestline = requestLine;
49 requestInfoP->method = httpMethod;
50 requestInfoP->host = host;
51 requestInfoP->port = port;
52 requestInfoP->uri = path;
53 requestInfoP->query = query;
54 requestInfoP->from = NULL;
55 requestInfoP->useragent = NULL;
56 requestInfoP->referer = NULL;
57 requestInfoP->user = NULL;
59 if (httpVersion.major > 1 ||
60 (httpVersion.major == 1 && httpVersion.minor >= 1))
61 requestInfoP->keepalive = TRUE;
63 requestInfoP->keepalive = FALSE;
69 freeRequestInfo(TRequestInfo * const requestInfoP) {
71 if (requestInfoP->requestline)
72 xmlrpc_strfree(requestInfoP->requestline);
74 if (requestInfoP->user)
75 xmlrpc_strfree(requestInfoP->user);
81 RequestInit(TSession * const sessionP,
82 TConn * const connectionP) {
86 sessionP->validRequest = false; /* Don't have valid request yet */
89 sessionP->date = *gmtime(&nowtime);
91 sessionP->conn = connectionP;
93 sessionP->responseStarted = FALSE;
95 sessionP->chunkedwrite = FALSE;
96 sessionP->chunkedwritemode = FALSE;
98 sessionP->continueRequired = FALSE;
100 ListInit(&sessionP->cookies);
101 ListInit(&sessionP->ranges);
102 TableInit(&sessionP->request_headers);
103 TableInit(&sessionP->response_headers);
105 sessionP->status = 0; /* No status from handler yet */
107 StringAlloc(&(sessionP->header));
113 RequestFree(TSession * const sessionP) {
115 if (sessionP->validRequest)
116 freeRequestInfo(&sessionP->request_info);
118 ListFree(&sessionP->cookies);
119 ListFree(&sessionP->ranges);
120 TableFree(&sessionP->request_headers);
121 TableFree(&sessionP->response_headers);
122 StringFree(&(sessionP->header));
128 readRequestLine(TSession * const sessionP,
129 char ** const requestLineP,
130 uint16_t * const httpErrorCodeP) {
134 /* Ignore CRLFs in the beginning of the request (RFC2068-P30) */
137 success = ConnReadHeader(sessionP->conn, requestLineP);
139 *httpErrorCodeP = 408; /* Request Timeout */
140 } while (!*httpErrorCodeP && (*requestLineP)[0] == '\0');
146 unescapeUri(char * const uri,
147 abyss_bool * const errorP) {
156 while (*x && !*errorP) {
162 if ((c >= '0') && (c <= '9'))
164 else if ((c >= 'a') && (c <= 'f'))
172 if ((d >= '0') && (d <= '9'))
174 else if ((d >= 'a') && (d <= 'f'))
180 *y++ = ((c << 4) | d);
195 parseHostPort(char * const hostport,
196 const char ** const hostP,
197 unsigned short * const portP,
198 uint16_t * const httpErrorCodeP) {
202 colonPos = strchr(hostport, ':');
207 *colonPos = '\0'; /* Split hostport at the colon */
211 for (p = colonPos + 1, port = 0;
212 isdigit(*p) && port < 65535;
213 (port = port * 10 + (*p - '0')), ++p);
218 *httpErrorCodeP = 400; /* Bad Request */
231 parseRequestUri(char * const requestUri,
232 const char ** const hostP,
233 const char ** const pathP,
234 const char ** const queryP,
235 unsigned short * const portP,
236 uint16_t * const httpErrorCodeP) {
237 /*----------------------------------------------------------------------------
238 Parse the request URI (in the request line
239 "GET http://www.myserver.com/myfile?parm HTTP/1.1",
240 "http://www.myserver.com/myfile?parm" is the request URI).
242 This destroys *requestUri and returns pointers into *requestUri!
244 This is extremely ugly. We need to redo it with dynamically allocated
245 storage. We should return individual malloc'ed strings.
246 -----------------------------------------------------------------------------*/
249 unescapeUri(requestUri, &error);
252 *httpErrorCodeP = 400; /* Bad Request */
254 char * requestUriNoQuery;
255 /* The request URI with any query (the stuff marked by a question
256 mark at the end of a request URI) chopped off.
259 /* Split requestUri at the question mark */
260 char * const qmark = strchr(requestUri, '?');
269 requestUriNoQuery = requestUri;
271 if (requestUriNoQuery[0] == '/') {
273 *pathP = requestUriNoQuery;
276 if (!xmlrpc_strneq(requestUriNoQuery, "http://", 7))
277 *httpErrorCodeP = 400; /* Bad Request */
279 char * const hostportpath = &requestUriNoQuery[7];
280 char * const slashPos = strchr(hostportpath, '/');
287 /* Nul-terminate the host name. To make space for
288 it, slide the whole name back one character.
289 This moves it into the space now occupied by
290 the end of "http://", which we don't need.
292 for (p = hostportpath; *p != '/'; ++p)
296 hostport = hostportpath - 1;
300 hostport = hostportpath;
303 if (!*httpErrorCodeP)
304 parseHostPort(hostport, hostP, portP, httpErrorCodeP);
313 parseRequestLine(char * const requestLine,
314 TMethod * const httpMethodP,
315 httpVersion * const httpVersionP,
316 const char ** const hostP,
317 unsigned short * const portP,
318 const char ** const pathP,
319 const char ** const queryP,
320 abyss_bool * const moreLinesP,
321 uint16_t * const httpErrorCodeP) {
322 /*----------------------------------------------------------------------------
323 Modifies *header1 and returns pointers to its storage!
324 -----------------------------------------------------------------------------*/
325 const char * httpMethodName;
330 /* Jump over spaces */
331 NextToken((const char **)&p);
333 httpMethodName = GetToken(&p);
335 *httpErrorCodeP = 400; /* Bad Request */
339 if (xmlrpc_streq(httpMethodName, "GET"))
340 *httpMethodP = m_get;
341 else if (xmlrpc_streq(httpMethodName, "PUT"))
342 *httpMethodP = m_put;
343 else if (xmlrpc_streq(httpMethodName, "OPTIONS"))
344 *httpMethodP = m_options;
345 else if (xmlrpc_streq(httpMethodName, "DELETE"))
346 *httpMethodP = m_delete;
347 else if (xmlrpc_streq(httpMethodName, "POST"))
348 *httpMethodP = m_post;
349 else if (xmlrpc_streq(httpMethodName, "TRACE"))
350 *httpMethodP = m_trace;
351 else if (xmlrpc_streq(httpMethodName, "HEAD"))
352 *httpMethodP = m_head;
354 *httpMethodP = m_unknown;
356 /* URI and Query Decoding */
357 NextToken((const char **)&p);
360 requestUri = GetToken(&p);
362 *httpErrorCodeP = 400; /* Bad Request */
364 parseRequestUri(requestUri, hostP, pathP, queryP, portP,
367 if (!*httpErrorCodeP) {
368 const char * httpVersion;
370 NextToken((const char **)&p);
372 /* HTTP Version Decoding */
374 httpVersion = GetToken(&p);
377 if (sscanf(httpVersion, "HTTP/%d.%d", &vmaj, &vmin) != 2)
378 *httpErrorCodeP = 400; /* Bad Request */
380 httpVersionP->major = vmaj;
381 httpVersionP->minor = vmin;
382 *httpErrorCodeP = 0; /* no error */
386 /* There is no HTTP version, so this is a single
389 *httpErrorCodeP = 0; /* no error */
400 strtolower(char * const s) {
414 getFieldNameToken(char ** const pP,
415 char ** const fieldNameP,
416 uint16_t * const httpErrorCodeP) {
420 NextToken((const char **)pP);
422 fieldName = GetToken(pP);
424 *httpErrorCodeP = 400; /* Bad Request */
426 if (fieldName[strlen(fieldName)-1] != ':')
427 /* Not a valid field name */
428 *httpErrorCodeP = 400; /* Bad Request */
430 fieldName[strlen(fieldName)-1] = '\0'; /* remove trailing colon */
432 strtolower(fieldName);
434 *httpErrorCodeP = 0; /* no error */
435 *fieldNameP = fieldName;
443 processHeader(const char * const fieldName,
444 char * const fieldValue,
445 TSession * const sessionP,
446 uint16_t * const httpErrorCodeP) {
447 /*----------------------------------------------------------------------------
448 We may modify *fieldValue, and we put pointers to *fieldValue and
449 *fieldName into *sessionP.
451 We must fix this some day. *sessionP should point to individual
453 -----------------------------------------------------------------------------*/
454 *httpErrorCodeP = 0; /* initial assumption */
456 if (xmlrpc_streq(fieldName, "connection")) {
457 if (xmlrpc_strcaseeq(fieldValue, "keep-alive"))
458 sessionP->request_info.keepalive = TRUE;
460 sessionP->request_info.keepalive = FALSE;
461 } else if (xmlrpc_streq(fieldName, "host"))
462 parseHostPort(fieldValue, &sessionP->request_info.host,
463 &sessionP->request_info.port, httpErrorCodeP);
464 else if (xmlrpc_streq(fieldName, "from"))
465 sessionP->request_info.from = fieldValue;
466 else if (xmlrpc_streq(fieldName, "user-agent"))
467 sessionP->request_info.useragent = fieldValue;
468 else if (xmlrpc_streq(fieldName, "referer"))
469 sessionP->request_info.referer = fieldValue;
470 else if (xmlrpc_streq(fieldName, "range")) {
471 if (xmlrpc_strneq(fieldValue, "bytes=", 6)) {
472 abyss_bool succeeded;
473 succeeded = ListAddFromString(&sessionP->ranges, &fieldValue[6]);
474 *httpErrorCodeP = succeeded ? 0 : 400;
476 } else if (xmlrpc_streq(fieldName, "cookies")) {
477 abyss_bool succeeded;
478 succeeded = ListAddFromString(&sessionP->cookies, fieldValue);
479 *httpErrorCodeP = succeeded ? 0 : 400;
480 } else if (xmlrpc_streq(fieldName, "expect")) {
481 if (xmlrpc_strcaseeq(fieldValue, "100-continue"))
482 sessionP->continueRequired = TRUE;
489 RequestRead(TSession * const sessionP) {
490 uint16_t httpErrorCode; /* zero for no error */
493 readRequestLine(sessionP, &requestLine, &httpErrorCode);
494 if (!httpErrorCode) {
500 abyss_bool moreHeaders;
502 parseRequestLine(requestLine, &httpMethod, &sessionP->version,
503 &host, &port, &path, &query,
504 &moreHeaders, &httpErrorCode);
507 initRequestInfo(&sessionP->request_info, sessionP->version,
509 httpMethod, host, port, path, query);
511 while (moreHeaders && !httpErrorCode) {
513 abyss_bool succeeded;
514 succeeded = ConnReadHeader(sessionP->conn, &p);
516 httpErrorCode = 408; /* Request Timeout */
519 /* We have reached the empty line so all the request
525 getFieldNameToken(&p, &fieldName, &httpErrorCode);
526 if (!httpErrorCode) {
529 NextToken((const char **)&p);
533 TableAdd(&sessionP->request_headers,
534 fieldName, fieldValue);
536 processHeader(fieldName, fieldValue, sessionP,
544 ResponseStatus(sessionP, httpErrorCode);
546 sessionP->validRequest = true;
548 return !httpErrorCode;
553 char *RequestHeaderValue(TSession *r,char *name)
555 return (TableFind(&r->request_headers,name));
561 RequestValidURI(TSession * const sessionP) {
563 if (!sessionP->request_info.uri)
566 if (xmlrpc_streq(sessionP->request_info.uri, "*"))
567 return (sessionP->request_info.method != m_options);
569 if (strchr(sessionP->request_info.uri, '*'))
578 RequestValidURIPath(TSession * const sessionP) {
583 p = sessionP->request_info.uri;
593 else if ((strncmp(p,"./",2) == 0) || (strcmp(p, ".") == 0))
595 else if ((strncmp(p, "../", 2) == 0) ||
596 (strcmp(p, "..") == 0)) {
602 /* Prevent accessing hidden files (starting with .) */
610 return (*p == 0 && i > 0);
616 RequestAuth(TSession *r,char *credential,char *user,char *pass) {
621 p=RequestHeaderValue(r,"authorization");
623 NextToken((const char **)&p);
626 if (strcasecmp(x,"basic")==0) {
627 NextToken((const char **)&p);
628 sprintf(z,"%s:%s",user,pass);
631 if (strcmp(p,t)==0) {
632 r->request_info.user=strdup(user);
639 sprintf(z,"Basic realm=\"%s\"",credential);
640 ResponseAddField(r,"WWW-Authenticate",z);
641 ResponseStatus(r,401);
647 /*********************************************************************
649 *********************************************************************/
651 abyss_bool RangeDecode(char *str,uint64_t filesize,uint64_t *start,uint64_t *end)
660 *start=filesize-strtol(str+1,&ss,10);
661 return ((ss!=str) && (!*ss));
664 *start=strtol(str,&ss,10);
666 if ((ss==str) || (*ss!='-'))
674 *end=strtol(str,&ss,10);
676 if ((ss==str) || (*ss) || (*end<*start))
682 /*********************************************************************
684 *********************************************************************/
687 HTTPReasonByStatus(uint16_t const code) {
689 struct _HTTPReasons {
694 static struct _HTTPReasons const reasons[] = {
696 { 101,"Switching Protocols" },
700 { 203,"Non-Authoritative Information" },
701 { 204,"No Content" },
702 { 205,"Reset Content" },
703 { 206,"Partial Content" },
704 { 300,"Multiple Choices" },
705 { 301,"Moved Permanently" },
706 { 302,"Moved Temporarily" },
708 { 304,"Not Modified" },
710 { 400,"Bad Request" },
711 { 401,"Unauthorized" },
712 { 402,"Payment Required" },
715 { 405,"Method Not Allowed" },
716 { 406,"Not Acceptable" },
717 { 407,"Proxy Authentication Required" },
718 { 408,"Request Timeout" },
721 { 411,"Length Required" },
722 { 412,"Precondition Failed" },
723 { 413,"Request Entity Too Large" },
724 { 414,"Request-URI Too Long" },
725 { 415,"Unsupported Media Type" },
726 { 500,"Internal Server Error" },
727 { 501,"Not Implemented" },
728 { 502,"Bad Gateway" },
729 { 503,"Service Unavailable" },
730 { 504,"Gateway Timeout" },
731 { 505,"HTTP Version Not Supported" },
734 const struct _HTTPReasons * reasonP;
736 reasonP = &reasons[0];
738 while (reasonP->status <= code)
739 if (reasonP->status == code)
740 return reasonP->reason;
750 HTTPRead(TSession * const s ATTR_UNUSED,
751 const char * const buffer ATTR_UNUSED,
752 uint32_t const len ATTR_UNUSED) {
760 HTTPWriteBodyChunk(TSession * const sessionP,
761 const char * const buffer,
762 uint32_t const len) {
764 abyss_bool succeeded;
766 if (sessionP->chunkedwrite && sessionP->chunkedwritemode) {
767 char chunkHeader[16];
769 sprintf(chunkHeader, "%x\r\n", len);
772 ConnWrite(sessionP->conn, chunkHeader, strlen(chunkHeader));
774 succeeded = ConnWrite(sessionP->conn, buffer, len);
776 succeeded = ConnWrite(sessionP->conn, "\r\n", 2);
779 succeeded = ConnWrite(sessionP->conn, buffer, len);
787 HTTPWriteEndChunk(TSession * const sessionP) {
791 if (sessionP->chunkedwritemode && sessionP->chunkedwrite) {
792 /* May be one day trailer dumping will be added */
793 sessionP->chunkedwritemode = FALSE;
794 retval = ConnWrite(sessionP->conn, "0\r\n\r\n", 5);
804 HTTPKeepalive(TSession * const sessionP) {
805 /*----------------------------------------------------------------------------
806 Return value: the connection should be kept alive after the session
808 -----------------------------------------------------------------------------*/
809 return (sessionP->request_info.keepalive &&
810 !sessionP->serverDeniesKeepalive &&
811 sessionP->status < 400);
817 HTTPWriteContinue(TSession * const sessionP) {
819 char const continueStatus[] = "HTTP/1.1 100 continue\r\n\r\n";
820 /* This is a status line plus an end-of-headers empty line */
822 return ConnWrite(sessionP->conn, continueStatus, strlen(continueStatus));
827 /******************************************************************************
831 ** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
832 ** All rights reserved.
834 ** Redistribution and use in source and binary forms, with or without
835 ** modification, are permitted provided that the following conditions
837 ** 1. Redistributions of source code must retain the above copyright
838 ** notice, this list of conditions and the following disclaimer.
839 ** 2. Redistributions in binary form must reproduce the above copyright
840 ** notice, this list of conditions and the following disclaimer in the
841 ** documentation and/or other materials provided with the distribution.
842 ** 3. The name of the author may not be used to endorse or promote products
843 ** derived from this software without specific prior written permission.
845 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
846 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
847 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
848 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
849 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
850 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
851 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
852 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
853 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
854 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
857 ******************************************************************************/