VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/http-server.cpp@ 100368

Last change on this file since 100368 was 100368, checked in by vboxsync, 18 months ago

Shared Clipboard: More work on making the internal APIs more fine grained and easier to follow [build fix]. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.7 KB
Line 
1/* $Id: http-server.cpp 100368 2023-07-04 16:26:04Z vboxsync $ */
2/** @file
3 * Simple HTTP server (RFC 7231) implementation.
4 *
5 * Known limitations so far:
6 * - Only HTTP 1.1.
7 * - Only supports GET + HEAD methods so far.
8 * - Only supports UTF-8 charset.
9 * - Only supports plain text and octet stream MIME types.
10 * - No content compression ("gzip", "x-gzip", ++).
11 * - No caching.
12 * - No redirections (via 302).
13 * - No encryption (TLS).
14 * - No IPv6 support.
15 * - No multi-threading.
16 *
17 * For WebDAV (optional via IPRT_HTTP_WITH_WEBDAV):
18 * - Only OPTIONS + PROPLIST methods are implemented (e.g. simple read-only support).
19 * - No pagination support for directory listings.
20 */
21
22/*
23 * Copyright (C) 2020-2023 Oracle and/or its affiliates.
24 *
25 * This file is part of VirtualBox base platform packages, as
26 * available from https://www.virtualbox.org.
27 *
28 * This program is free software; you can redistribute it and/or
29 * modify it under the terms of the GNU General Public License
30 * as published by the Free Software Foundation, in version 3 of the
31 * License.
32 *
33 * This program is distributed in the hope that it will be useful, but
34 * WITHOUT ANY WARRANTY; without even the implied warranty of
35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
36 * General Public License for more details.
37 *
38 * You should have received a copy of the GNU General Public License
39 * along with this program; if not, see <https://www.gnu.org/licenses>.
40 *
41 * The contents of this file may alternatively be used under the terms
42 * of the Common Development and Distribution License Version 1.0
43 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
44 * in the VirtualBox distribution, in which case the provisions of the
45 * CDDL are applicable instead of those of the GPL.
46 *
47 * You may elect to license modified versions of this file under the
48 * terms and conditions of either the GPL or the CDDL or both.
49 *
50 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
51 */
52
53
54/*********************************************************************************************************************************
55* Header Files *
56*********************************************************************************************************************************/
57#define LOG_GROUP RTLOGGROUP_HTTP
58#include <iprt/http.h>
59#include <iprt/http-server.h>
60#include "internal/iprt.h"
61#include "internal/magics.h"
62
63#include <iprt/asm.h>
64#include <iprt/assert.h>
65#include <iprt/circbuf.h>
66#include <iprt/ctype.h>
67#include <iprt/err.h>
68#include <iprt/file.h> /* For file mode flags. */
69#include <iprt/getopt.h>
70#include <iprt/mem.h>
71#include <iprt/log.h>
72#include <iprt/path.h>
73#include <iprt/poll.h>
74#include <iprt/socket.h>
75#include <iprt/sort.h>
76#include <iprt/string.h>
77#include <iprt/system.h>
78#include <iprt/tcp.h>
79
80#define LOG_GROUP RTLOGGROUP_HTTP
81#include <iprt/log.h>
82
83
84/*********************************************************************************************************************************
85* Structures and Typedefs *
86*********************************************************************************************************************************/
87/**
88 * Internal HTTP server instance.
89 */
90typedef struct RTHTTPSERVERINTERNAL
91{
92 /** Magic value. */
93 uint32_t u32Magic;
94 /** Callback table. */
95 RTHTTPSERVERCALLBACKS Callbacks;
96 /** Pointer to TCP server instance. */
97 PRTTCPSERVER pTCPServer;
98 /** Pointer to user-specific data. Optional. */
99 void *pvUser;
100 /** Size of user-specific data. Optional. */
101 size_t cbUser;
102} RTHTTPSERVERINTERNAL;
103/** Pointer to an internal HTTP server instance. */
104typedef RTHTTPSERVERINTERNAL *PRTHTTPSERVERINTERNAL;
105
106
107/*********************************************************************************************************************************
108* Defined Constants And Macros *
109*********************************************************************************************************************************/
110/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
111#define RTHTTPSERVER_VALID_RETURN_RC(hHttpServer, a_rc) \
112 do { \
113 AssertPtrReturn((hHttpServer), (a_rc)); \
114 AssertReturn((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC, (a_rc)); \
115 } while (0)
116
117/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
118#define RTHTTPSERVER_VALID_RETURN(hHttpServer) RTHTTPSERVER_VALID_RETURN_RC((hHttpServer), VERR_INVALID_HANDLE)
119
120/** Validates a handle and returns (void) if not valid. */
121#define RTHTTPSERVER_VALID_RETURN_VOID(hHttpServer) \
122 do { \
123 AssertPtrReturnVoid(hHttpServer); \
124 AssertReturnVoid((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC); \
125 } while (0)
126
127
128/** Handles a HTTP server callback with no arguments and returns. */
129#define RTHTTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
130 do \
131 { \
132 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
133 if (pCallbacks->a_Name) \
134 { \
135 RTHTTPCALLBACKDATA Data = { &pClient->State }; \
136 return pCallbacks->a_Name(&Data); \
137 } \
138 return VERR_NOT_IMPLEMENTED; \
139 } while (0)
140
141/** Handles a HTTP server callback with no arguments and sets rc accordingly. */
142#define RTHTTPSERVER_HANDLE_CALLBACK(a_Name) \
143 do \
144 { \
145 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
146 if (pCallbacks->a_Name) \
147 { \
148 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
149 rc = pCallbacks->a_Name(&Data); \
150 } \
151 } while (0)
152
153/** Handles a HTTP server callback with arguments and sets rc accordingly. */
154#define RTHTTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
155 do \
156 { \
157 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
158 if (pCallbacks->a_Name) \
159 { \
160 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
161 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
162 } \
163 } while (0)
164
165/** Handles a HTTP server callback with arguments and returns. */
166#define RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
167 do \
168 { \
169 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
170 if (pCallbacks->a_Name) \
171 { \
172 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
173 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
174 } \
175 } while (0)
176
177
178/*********************************************************************************************************************************
179* Structures and Typedefs *
180*********************************************************************************************************************************/
181
182/**
183 * Structure for maintaining an internal HTTP server client.
184 */
185typedef struct RTHTTPSERVERCLIENT
186{
187 /** Pointer to internal server state. */
188 PRTHTTPSERVERINTERNAL pServer;
189 /** Socket handle the client is bound to. */
190 RTSOCKET hSocket;
191 /** Actual client state. */
192 RTHTTPSERVERCLIENTSTATE State;
193} RTHTTPSERVERCLIENT;
194/** Pointer to an internal HTTP server client state. */
195typedef RTHTTPSERVERCLIENT *PRTHTTPSERVERCLIENT;
196
197/** Function pointer declaration for a specific HTTP server method handler. */
198typedef DECLCALLBACKTYPE(int, FNRTHTTPSERVERMETHOD,(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq));
199/** Pointer to a FNRTHTTPSERVERMETHOD(). */
200typedef FNRTHTTPSERVERMETHOD *PFNRTHTTPSERVERMETHOD;
201
202/**
203 * Static lookup table for some file extensions <-> MIME type. Add more as needed.
204 * Keep this alphabetical (file extension).
205 */
206static const struct
207{
208 /** File extension. */
209 const char *pszExt;
210 /** MIME type. */
211 const char *pszMIMEType;
212} s_aFileExtMIMEType[] = {
213 { ".arj", "application/x-arj-compressed" },
214 { ".asf", "video/x-ms-asf" },
215 { ".avi", "video/x-msvideo" },
216 { ".bmp", "image/bmp" },
217 { ".css", "text/css" },
218 { ".doc", "application/msword" },
219 { ".exe", "application/octet-stream" },
220 { ".gif", "image/gif" },
221 { ".gz", "application/x-gunzip" },
222 { ".htm", "text/html" },
223 { ".html", "text/html" },
224 { ".ico", "image/x-icon" },
225 { ".js", "application/x-javascript" },
226 { ".json", "text/json" },
227 { ".jpg", "image/jpeg" },
228 { ".jpeg", "image/jpeg" },
229 { ".ogg", "application/ogg" },
230 { ".m3u", "audio/x-mpegurl" },
231 { ".m4v", "video/x-m4v" },
232 { ".mid", "audio/mid" },
233 { ".mov", "video/quicktime" },
234 { ".mp3", "audio/x-mp3" },
235 { ".mp4", "video/mp4" },
236 { ".mpg", "video/mpeg" },
237 { ".mpeg", "video/mpeg" },
238 { ".pdf", "application/pdf" },
239 { ".png", "image/png" },
240 { ".ra", "audio/x-pn-realaudio" },
241 { ".ram", "audio/x-pn-realaudio" },
242 { ".rar", "application/x-arj-compressed" },
243 { ".rtf", "application/rtf" },
244 { ".shtm", "text/html" },
245 { ".shtml", "text/html" },
246 { ".svg", "image/svg+xml" },
247 { ".swf", "application/x-shockwave-flash" },
248 { ".torrent", "application/x-bittorrent" },
249 { ".tar", "application/x-tar" },
250 { ".tgz", "application/x-tar-gz" },
251 { ".ttf", "application/x-font-ttf" },
252 { ".txt", "text/plain" },
253 { ".wav", "audio/x-wav" },
254 { ".webm", "video/webm" },
255 { ".xml", "text/xml" },
256 { ".xls", "application/excel" },
257 { ".xsl", "application/xml" },
258 { ".xslt", "application/xml" },
259 { ".zip", "application/x-zip-compressed" },
260 { NULL, NULL }
261};
262
263
264/*********************************************************************************************************************************
265* Internal Functions *
266*********************************************************************************************************************************/
267
268/** @name Method handlers.
269 * @{
270 */
271static FNRTHTTPSERVERMETHOD rtHttpServerHandleGET;
272static FNRTHTTPSERVERMETHOD rtHttpServerHandleHEAD;
273#ifdef IPRT_HTTP_WITH_WEBDAV
274 static FNRTHTTPSERVERMETHOD rtHttpServerHandleOPTIONS;
275 static FNRTHTTPSERVERMETHOD rtHttpServerHandlePROPFIND;
276#endif
277/** @} */
278
279/**
280 * Structure for maintaining a single method entry for the methods table.
281 */
282typedef struct RTHTTPSERVERMETHOD_ENTRY
283{
284 /** Method ID. */
285 RTHTTPMETHOD enmMethod;
286 /** Function pointer invoked to handle the command. */
287 PFNRTHTTPSERVERMETHOD pfnMethod;
288} RTHTTPSERVERMETHOD_ENTRY;
289/** Pointer to a command entry. */
290typedef RTHTTPSERVERMETHOD_ENTRY *PRTHTTPMETHOD_ENTRY;
291
292
293
294/*********************************************************************************************************************************
295* Global Variables *
296*********************************************************************************************************************************/
297/**
298 * Table of handled methods.
299 */
300static const RTHTTPSERVERMETHOD_ENTRY g_aMethodMap[] =
301{
302 { RTHTTPMETHOD_GET, rtHttpServerHandleGET },
303 { RTHTTPMETHOD_HEAD, rtHttpServerHandleHEAD },
304#ifdef IPRT_HTTP_WITH_WEBDAV
305 { RTHTTPMETHOD_OPTIONS, rtHttpServerHandleOPTIONS },
306 { RTHTTPMETHOD_PROPFIND, rtHttpServerHandlePROPFIND },
307#endif
308 { RTHTTPMETHOD_END, NULL }
309};
310
311/** Maximum length in characters a HTTP server path can have (excluding termination). */
312#define RTHTTPSERVER_MAX_PATH RTPATH_MAX
313
314
315/*********************************************************************************************************************************
316* Internal functions *
317*********************************************************************************************************************************/
318
319/**
320 * Guesses the HTTP MIME type based on a given file extension.
321 *
322 * Note: Has to include the beginning dot, e.g. ".mp3" (see IPRT).
323 *
324 * @returns Guessed MIME type, or "application/octet-stream" if not found.
325 * @param pszFileExt File extension to guess MIME type for.
326 */
327static const char *rtHttpServerGuessMIMEType(const char *pszFileExt)
328{
329 if (pszFileExt)
330 {
331 size_t i = 0;
332 while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */
333 {
334 if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt))
335 return s_aFileExtMIMEType[i].pszMIMEType;
336 }
337 }
338
339 return "application/octet-stream";
340}
341
342/**
343 * Initializes a HTTP body.
344 *
345 * @param pBody Body to initialize.
346 * @param cbSize Size of body (in bytes) to allocate. Optional and can be 0.
347 */
348static int rtHttpServerBodyInit(PRTHTTPBODY pBody, size_t cbSize)
349{
350 if (cbSize)
351 {
352 pBody->pvBody = RTMemAlloc(cbSize);
353 AssertPtrReturn(pBody->pvBody, VERR_NO_MEMORY);
354 pBody->cbBodyAlloc = cbSize;
355 }
356 else
357 {
358 pBody->pvBody = NULL;
359 pBody->cbBodyAlloc = 0;
360 }
361
362 pBody->cbBodyUsed = 0;
363 pBody->offBody = 0;
364
365 return VINF_SUCCESS;
366}
367
368/**
369 * Destroys a HTTP body.
370 *
371 * @param pBody Body to destroy.
372 */
373static void rtHttpServerBodyDestroy(PRTHTTPBODY pBody)
374{
375 if (!pBody)
376 return;
377
378 if (pBody->pvBody)
379 {
380 Assert(pBody->cbBodyAlloc);
381
382 RTMemFree(pBody->pvBody);
383 pBody->pvBody = NULL;
384 }
385
386 pBody->cbBodyAlloc = 0;
387 pBody->cbBodyUsed = 0;
388 pBody->offBody = 0;
389}
390
391/**
392 * Allocates and initializes a new client request.
393 *
394 * @returns Pointer to the new client request, or NULL on OOM.
395 * Needs to be free'd with rtHttpServerReqFree().
396 */
397static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void)
398{
399 PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ));
400 AssertPtrReturn(pReq, NULL);
401
402 int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst);
403 AssertRC(rc2);
404
405 rc2 = rtHttpServerBodyInit(&pReq->Body, 0 /* cbSize */);
406 AssertRC(rc2);
407
408 return pReq;
409}
410
411/**
412 * Frees a formerly allocated client request.
413 *
414 * @param pReq Pointer to client request to free.
415 * The pointer will be invalid on return.
416 */
417static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq)
418{
419 if (!pReq)
420 return;
421
422 RTStrFree(pReq->pszUrl);
423
424 RTHttpHeaderListDestroy(pReq->hHdrLst);
425
426 rtHttpServerBodyDestroy(&pReq->Body);
427
428 RTMemFree(pReq);
429 pReq = NULL;
430}
431
432/**
433 * Initializes a HTTP server response with an allocated body size.
434 *
435 * @returns VBox status code.
436 * @param pResp HTTP server response to initialize.
437 * @param cbBody Body size (in bytes) to allocate.
438 */
439RTR3DECL(int) RTHttpServerResponseInitEx(PRTHTTPSERVERRESP pResp, size_t cbBody)
440{
441 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
442
443 int rc = RTHttpHeaderListInit(&pResp->hHdrLst);
444 AssertRCReturn(rc, rc);
445
446 rc = rtHttpServerBodyInit(&pResp->Body, cbBody);
447
448 return rc;
449}
450
451/**
452 * Initializes a HTTP server response.
453 *
454 * @returns VBox status code.
455 * @param pResp HTTP server response to initialize.
456 */
457RTR3DECL(int) RTHttpServerResponseInit(PRTHTTPSERVERRESP pResp)
458{
459 return RTHttpServerResponseInitEx(pResp, 0 /* cbBody */);
460}
461
462/**
463 * Destroys a formerly initialized HTTP server response.
464 *
465 * @param pResp Pointer to HTTP server response to destroy.
466 */
467RTR3DECL(void) RTHttpServerResponseDestroy(PRTHTTPSERVERRESP pResp)
468{
469 if (!pResp)
470 return;
471
472 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
473
474 RTHttpHeaderListDestroy(pResp->hHdrLst);
475
476 rtHttpServerBodyDestroy(&pResp->Body);
477}
478
479
480/*********************************************************************************************************************************
481* Protocol Functions *
482*********************************************************************************************************************************/
483
484/**
485 * Logs the HTTP protocol communication to the debug logger (2).
486 *
487 * @param pClient Client to log communication for.
488 * @param fWrite Whether the server is writing (to client) or reading (from client).
489 * @param pszData Actual protocol communication data to log.
490 */
491static void rtHttpServerLogProto(PRTHTTPSERVERCLIENT pClient, bool fWrite, const char *pszData)
492{
493 RT_NOREF(pClient);
494
495#ifdef LOG_ENABLED
496 if (!pszData) /* Nothing to log? Bail out. */
497 return;
498
499 char **ppapszStrings;
500 size_t cStrings;
501 int rc2 = RTStrSplit(pszData, strlen(pszData), RTHTTPSERVER_HTTP11_EOL_STR, &ppapszStrings, &cStrings);
502 if (RT_SUCCESS(rc2))
503 {
504 for (size_t i = 0; i < cStrings; i++)
505 {
506 Log2(("%s %s\n", fWrite ? ">" : "<", ppapszStrings[i]));
507 RTStrFree(ppapszStrings[i]);
508 }
509
510 RTMemFree(ppapszStrings);
511 }
512#else
513 RT_NOREF(fWrite, pszData);
514#endif
515}
516
517/**
518 * Writes HTTP protocol communication data to a connected client.
519 *
520 * @returns VBox status code.
521 * @param pClient Client to write data to.
522 * @param pszData Data to write. Must be zero-terminated.
523 */
524static int rtHttpServerWriteProto(PRTHTTPSERVERCLIENT pClient, const char *pszData)
525{
526 rtHttpServerLogProto(pClient, true /* fWrite */, pszData);
527 return RTTcpWrite(pClient->hSocket, pszData, strlen(pszData));
528}
529
530/**
531 * Main function for sending a response back to the client.
532 *
533 * @returns VBox status code.
534 * @param pClient Client to reply to.
535 * @param enmSts Status code to send.
536 */
537static int rtHttpServerSendResponse(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
538{
539 char *pszResp;
540 int rc = RTStrAPrintf(&pszResp,
541 "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
542 AssertRCReturn(rc, rc);
543 rc = rtHttpServerWriteProto(pClient, pszResp);
544 RTStrFree(pszResp);
545
546 LogFlowFuncLeaveRC(rc);
547 return rc;
548}
549
550/**
551 * Main function for sending response headers back to the client.
552 *
553 * @returns VBox status code.
554 * @param pClient Client to reply to.
555 * @param pHdrLst Header list to send. Optional and can be NULL.
556 */
557static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient, PRTHTTPHEADERLIST pHdrLst)
558{
559 RTHTTPHEADERLIST HdrLst;
560 int rc = RTHttpHeaderListInit(&HdrLst);
561 AssertRCReturn(rc, rc);
562
563#ifdef DEBUG
564 /* Include a timestamp when running a debug build. */
565 RTTIMESPEC tsNow;
566 char szTS[64];
567 RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
568 rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
569 AssertRCReturn(rc, rc);
570#endif
571
572 /* Note: Deliberately don't include the VBox version due to security reasons. */
573 rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
574 AssertRCReturn(rc, rc);
575
576#ifdef IPRT_HTTP_WITH_WEBDAV
577 rc = RTHttpHeaderListAdd(HdrLst, "Allow", "GET, HEAD, PROPFIND", strlen("GET, HEAD, PROPFIND"), RTHTTPHEADERLISTADD_F_BACK);
578 AssertRCReturn(rc, rc);
579 rc = RTHttpHeaderListAdd(HdrLst, "DAV", "1", strlen("1"), RTHTTPHEADERLISTADD_F_BACK); /* Note: v1 is sufficient for read-only access. */
580 AssertRCReturn(rc, rc);
581#endif
582
583 char *pszHdr = NULL;
584
585 size_t i = 0;
586 const char *pszEntry;
587 while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
588 {
589 rc = RTStrAAppend(&pszHdr, pszEntry);
590 AssertRCBreak(rc);
591 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
592 AssertRCBreak(rc);
593 }
594
595 /* Append optional headers, if any. */
596 if (pHdrLst)
597 {
598 i = 0;
599 while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
600 {
601 rc = RTStrAAppend(&pszHdr, pszEntry);
602 AssertRCBreak(rc);
603 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
604 AssertRCBreak(rc);
605 }
606 }
607
608 if (RT_SUCCESS(rc))
609 {
610 /* Append trailing EOL. */
611 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
612 if (RT_SUCCESS(rc))
613 rc = rtHttpServerWriteProto(pClient, pszHdr);
614 }
615
616 RTStrFree(pszHdr);
617
618 RTHttpHeaderListDestroy(HdrLst);
619
620 LogFlowFunc(("rc=%Rrc\n", rc));
621 return rc;
622}
623
624/**
625 * Replies with (three digit) response status back to the client, extended version.
626 *
627 * @returns VBox status code.
628 * @param pClient Client to reply to.
629 * @param enmSts Status code to send.
630 * @param pHdrLst Header list to send. Optional and can be NULL.
631 */
632static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
633{
634 int rc = rtHttpServerSendResponse(pClient, enmSts);
635 if (RT_SUCCESS(rc))
636 rc = rtHttpServerSendResponseHdrEx(pClient, pHdrLst);
637
638 return rc;
639}
640
641/**
642 * Replies with (three digit) response status back to the client.
643 *
644 * @returns VBox status code.
645 * @param pClient Client to reply to.
646 * @param enmSts Status code to send.
647 */
648static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
649{
650 return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
651}
652
653/**
654 * Sends a chunk of the response body to the client.
655 *
656 * @returns VBox status code.
657 * @param pClient Client to send body to.
658 * @param pvBuf Data buffer to send.
659 * @param cbBuf Size (in bytes) of data buffer to send.
660 * @param pcbSent Where to store the sent bytes. Optional and can be NULL.
661 */
662static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
663{
664 int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
665 if ( RT_SUCCESS(rc)
666 && pcbSent)
667 *pcbSent = cbBuf;
668
669 return rc;
670}
671
672/**
673 * Resolves a VBox status code to a HTTP status code.
674 *
675 * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve.
676 * @param rc VBox status code to resolve.
677 */
678static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
679{
680 switch (rc)
681 {
682 case VINF_SUCCESS: return RTHTTPSTATUS_OK;
683 case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
684 case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
685 case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
686 case VERR_NOT_SUPPORTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
687 case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
688 case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
689 case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
690 case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
691 case VERR_INTERNAL_ERROR: return RTHTTPSTATUS_INTERNALSERVERERROR;
692 default:
693 break;
694 }
695
696 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
697 return RTHTTPSTATUS_INTERNALSERVERERROR;
698}
699
700
701/*********************************************************************************************************************************
702* Command Protocol Handlers *
703*********************************************************************************************************************************/
704
705/**
706 * Handler for the GET method.
707 *
708 * @returns VBox status code.
709 * @param pClient Client to handle GET method for.
710 * @param pReq Client request to handle.
711 */
712static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
713{
714 LogFlowFuncEnter();
715
716 /* If a low-level GET request handler is defined, call it and return. */
717 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
718
719 RTFSOBJINFO fsObj;
720 RT_ZERO(fsObj); /* Shut up MSVC. */
721
722 char *pszMIMEHint = NULL;
723
724 RTHTTPSTATUS enmStsResponse = RTHTTPSTATUS_OK;
725
726 int rc = VINF_SUCCESS;
727
728 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, &pszMIMEHint);
729 if (RT_FAILURE(rc))
730 enmStsResponse = rtHttpServerRcToStatus(rc);
731
732 void *pvHandle = NULL;
733 if (RT_SUCCESS(rc)) /* Only call open if querying information above succeeded. */
734 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
735
736 size_t cbBuf = _64K;
737 void *pvBuf = RTMemAlloc(cbBuf);
738 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
739
740 for (;;)
741 {
742 RTHTTPHEADERLIST HdrLst;
743 rc = RTHttpHeaderListInit(&HdrLst);
744 AssertRCReturn(rc, rc);
745
746 char szVal[16];
747
748 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
749 * of the body data for the directory listing. */
750
751 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
752 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
753 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
754 AssertRCBreak(rc);
755
756 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
757 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
758 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
759 AssertRCBreak(rc);
760
761 if (pszMIMEHint == NULL)
762 {
763 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
764 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
765 }
766 else
767 {
768 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK);
769 RTStrFree(pszMIMEHint);
770 pszMIMEHint = NULL;
771 }
772 AssertRCBreak(rc);
773
774 if (pClient->State.msKeepAlive)
775 {
776 /* If the client requested to keep alive the connection,
777 * always override this with 30s and report this back to the client. */
778 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Make this configurable. */
779#ifdef DEBUG_andy
780 pClient->State.msKeepAlive = 5000;
781#endif
782 cch = RTStrPrintf2(szVal, sizeof(szVal), "timeout=%RU64", pClient->State.msKeepAlive / RT_MS_1SEC); /** @todo No pipelining support here yet. */
783 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
784 rc = RTHttpHeaderListAdd(HdrLst, "Keep-Alive", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
785 AssertRCReturn(rc, rc);
786 }
787
788 rc = rtHttpServerSendResponseEx(pClient, enmStsResponse, &HdrLst);
789
790 RTHttpHeaderListDestroy(HdrLst);
791
792 if (rc == VERR_BROKEN_PIPE) /* Could happen on fast reloads. */
793 break;
794 AssertRCReturn(rc, rc);
795
796 size_t cbToRead = fsObj.cbObject;
797 size_t cbRead = 0; /* Shut up GCC. */
798 size_t cbWritten = 0; /* Ditto. */
799 while (cbToRead)
800 {
801 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
802 if (RT_FAILURE(rc))
803 break;
804 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
805 AssertBreak(cbToRead >= cbWritten);
806 cbToRead -= cbWritten;
807 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
808 {
809 rc = VINF_SUCCESS;
810 break;
811 }
812 AssertRCBreak(rc);
813 }
814
815 break;
816 } /* for (;;) */
817
818 RTMemFree(pvBuf);
819
820 int rc2 = rc; /* Save rc. */
821
822 if (pvHandle)
823 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
824
825 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
826 rc = rc2;
827
828 LogFlowFuncLeaveRC(rc);
829 return rc;
830}
831
832/**
833 * Handler for the HEAD method.
834 *
835 * @returns VBox status code.
836 * @param pClient Client to handle HEAD method for.
837 * @param pReq Client request to handle.
838 */
839static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
840{
841 LogFlowFuncEnter();
842
843 /* If a low-level HEAD request handler is defined, call it and return. */
844 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
845
846 int rc = VINF_SUCCESS;
847
848 RTFSOBJINFO fsObj;
849 RT_ZERO(fsObj); /* Shut up MSVC. */
850
851 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
852 if (RT_SUCCESS(rc))
853 {
854 RTHTTPHEADERLIST HdrLst;
855 rc = RTHttpHeaderListInit(&HdrLst);
856 AssertRCReturn(rc, rc);
857
858 /*
859 * Note: A response to a HEAD request does not have a body.
860 * All entity headers below are assumed to describe the the response a similar GET
861 * request would return (but then with a body).
862 */
863 char szVal[16];
864
865 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
866 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
867 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
868 AssertRCReturn(rc, rc);
869
870 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
871 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
872 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
873 AssertRCReturn(rc, rc);
874
875 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
876 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
877 AssertRCReturn(rc, rc);
878
879 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
880 AssertRCReturn(rc, rc);
881
882 RTHttpHeaderListDestroy(HdrLst);
883 }
884
885 LogFlowFuncLeaveRC(rc);
886 return rc;
887}
888
889#ifdef IPRT_HTTP_WITH_WEBDAV
890/**
891 * Handler for the OPTIONS method.
892 *
893 * @returns VBox status code.
894 * @param pClient Client to handle OPTIONS method for.
895 * @param pReq Client request to handle.
896 */
897static DECLCALLBACK(int) rtHttpServerHandleOPTIONS(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
898{
899 LogFlowFuncEnter();
900
901 RT_NOREF(pReq);
902
903 int rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, NULL /* pHdrLst */);
904
905 LogFlowFuncLeaveRC(rc);
906 return rc;
907}
908
909/**
910 * Handler for the PROPFIND (WebDAV) method.
911 *
912 * @returns VBox status code.
913 * @param pClient Client to handle PROPFIND method for.
914 * @param pReq Client request to handle.
915 */
916static DECLCALLBACK(int) rtHttpServerHandlePROPFIND(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
917{
918 LogFlowFuncEnter();
919
920 int rc = VINF_SUCCESS;
921
922 /* If a low-level GET request handler is defined, call it and return. */
923 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
924
925 RTFSOBJINFO fsObj;
926 RT_ZERO(fsObj); /* Shut up MSVC. */
927
928 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
929 if (RT_FAILURE(rc))
930 return rc;
931
932 void *pvHandle = NULL;
933 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
934
935 if (RT_SUCCESS(rc))
936 {
937 size_t cbBuf = _64K;
938 void *pvBuf = RTMemAlloc(cbBuf);
939 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
940
941 for (;;)
942 {
943 RTHTTPHEADERLIST HdrLst;
944 rc = RTHttpHeaderListInit(&HdrLst);
945 AssertRCReturn(rc, rc);
946
947 char szVal[16];
948
949 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", "text/xml; charset=utf-8", strlen("text/xml; charset=utf-8"), RTHTTPHEADERLISTADD_F_BACK);
950 AssertRCBreak(rc);
951
952 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
953 * of the body data for the directory listing. */
954
955 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
956 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
957 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
958 AssertRCBreak(rc);
959
960 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_MULTISTATUS, &HdrLst);
961 AssertRCReturn(rc, rc);
962
963 RTHttpHeaderListDestroy(HdrLst);
964
965 size_t cbToRead = fsObj.cbObject;
966 size_t cbRead = 0; /* Shut up GCC. */
967 size_t cbWritten = 0; /* Ditto. */
968 while (cbToRead)
969 {
970 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
971 if (RT_FAILURE(rc))
972 break;
973 //rtHttpServerLogProto(pClient, true /* fWrite */, (const char *)pvBuf);
974 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
975 AssertBreak(cbToRead >= cbWritten);
976 cbToRead -= cbWritten;
977 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
978 {
979 rc = VINF_SUCCESS;
980 break;
981 }
982 AssertRCBreak(rc);
983 }
984
985 break;
986 } /* for (;;) */
987
988 RTMemFree(pvBuf);
989
990 int rc2 = rc; /* Save rc. */
991
992 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
993
994 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
995 rc = rc2;
996 }
997
998 LogFlowFuncLeaveRC(rc);
999 return rc;
1000}
1001#endif /* IPRT_HTTP_WITH_WEBDAV */
1002
1003/**
1004 * Validates if a given path is valid or not.
1005 *
1006 * @returns \c true if path is valid, or \c false if not.
1007 * @param pszPath Path to check.
1008 * @param fIsAbsolute Whether the path to check is an absolute path or not.
1009 */
1010static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
1011{
1012 if (!pszPath)
1013 return false;
1014
1015 bool fIsValid = strlen(pszPath)
1016 && RTStrIsValidEncoding(pszPath)
1017 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
1018 if ( fIsValid
1019 && fIsAbsolute)
1020 {
1021 RTFSOBJINFO objInfo;
1022 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
1023 if (RT_SUCCESS(rc2))
1024 {
1025 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
1026 || RTFS_IS_FILE(objInfo.Attr.fMode);
1027
1028 /* No symlinks and other stuff allowed. */
1029 }
1030 else
1031 fIsValid = false;
1032 }
1033
1034 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
1035 return fIsValid;
1036
1037}
1038
1039/**
1040 * Parses headers and sets (replaces) a given header list.
1041 *
1042 * @returns VBox status code.
1043 * @param hList Header list to fill parsed headers in.
1044 * @param cStrings Number of strings to parse for \a papszStrings.
1045 * @param papszStrings Array of strings to parse.
1046 */
1047static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, size_t cStrings, char **papszStrings)
1048{
1049 /* Nothing to parse left? Bail out early. */
1050 if ( !cStrings
1051 || !papszStrings)
1052 return VINF_SUCCESS;
1053
1054#ifdef LOG_ENABLED
1055 for (size_t i = 0; i < cStrings; i++)
1056 LogFlowFunc(("Header: %s\n", papszStrings[i]));
1057#endif
1058
1059 int rc = RTHttpHeaderListSet(hList, cStrings, papszStrings);
1060
1061 LogFlowFunc(("rc=%Rrc, cHeaders=%zu\n", rc, cStrings));
1062 return rc;
1063}
1064
1065/**
1066 * Main function for parsing and allocating a client request.
1067 *
1068 * See: https://tools.ietf.org/html/rfc2616#section-2.2
1069 *
1070 * @returns VBox status code.
1071 * @param pClient Client to parse request from.
1072 * @param pszReq Request string with headers to parse.
1073 * @param cbReq Size (in bytes) of request string to parse.
1074 * @param ppReq Where to store the allocated client request on success.
1075 * Needs to be free'd via rtHttpServerReqFree().
1076 */
1077static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, const char *pszReq, size_t cbReq,
1078 PRTHTTPSERVERREQ *ppReq)
1079{
1080 RT_NOREF(pClient);
1081
1082 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
1083 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
1084
1085 /* We only support UTF-8 charset for now. */
1086 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
1087
1088 char **ppapszReq = NULL;
1089 size_t cReq = 0;
1090 int rc = RTStrSplit(pszReq, cbReq, RTHTTPSERVER_HTTP11_EOL_STR, &ppapszReq, &cReq);
1091 if (RT_FAILURE(rc))
1092 return rc;
1093
1094 if (!cReq)
1095 return VERR_INVALID_PARAMETER;
1096
1097#ifdef LOG_ENABLED
1098 for (size_t i = 0; i < cReq; i++)
1099 LogFlowFunc(("%s\n", ppapszReq[i]));
1100#endif
1101
1102 PRTHTTPSERVERREQ pReq = NULL;
1103
1104 char **ppapszFirstLine = NULL;
1105 size_t cFirstLine = 0;
1106 rc = RTStrSplit(ppapszReq[0], strlen(ppapszReq[0]), " ", &ppapszFirstLine, &cFirstLine);
1107 if (RT_SUCCESS(rc))
1108 {
1109 if (cFirstLine < 3) /* At leat the method, path and version has to be present. */
1110 rc = VERR_INVALID_PARAMETER;
1111 }
1112
1113 while (RT_SUCCESS(rc)) /* To use break. */
1114 {
1115 pReq = rtHttpServerReqAlloc();
1116 AssertPtrBreakStmt(pReq, rc = VERR_NO_MEMORY);
1117
1118 /*
1119 * Parse method to use. Method names are case sensitive.
1120 */
1121 const char *pszMethod = ppapszFirstLine[0];
1122 if (!RTStrCmp(pszMethod, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
1123 else if (!RTStrCmp(pszMethod, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
1124#ifdef IPRT_HTTP_WITH_WEBDAV
1125 else if (!RTStrCmp(pszMethod, "OPTIONS")) pReq->enmMethod = RTHTTPMETHOD_OPTIONS;
1126 else if (!RTStrCmp(pszMethod, "PROPFIND")) pReq->enmMethod = RTHTTPMETHOD_PROPFIND;
1127#endif
1128 else
1129 {
1130 rc = VERR_NOT_SUPPORTED;
1131 break;
1132 }
1133
1134 /*
1135 * Parse requested path.
1136 */
1137 /** @todo Do URL unescaping here. */
1138 const char *pszPath = ppapszFirstLine[1];
1139 if (!rtHttpServerPathIsValid(pszPath, false /* fIsAbsolute */))
1140 {
1141 rc = VERR_PATH_NOT_FOUND;
1142 break;
1143 }
1144
1145 pReq->pszUrl = RTStrDup(pszPath);
1146
1147 /*
1148 * Parse HTTP version to use.
1149 * We're picky here: Only HTTP 1.1 is supported by now.
1150 */
1151 const char *pszVer = ppapszFirstLine[2];
1152 if (RTStrCmp(pszVer, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
1153 {
1154 rc = VERR_NOT_SUPPORTED;
1155 break;
1156 }
1157
1158 /** @todo Anything else needed for the first line here? */
1159
1160 /*
1161 * Process headers, if any.
1162 */
1163 if (cReq > 1)
1164 {
1165 rc = rtHttpServerParseHeaders(pReq->hHdrLst, cReq - 1, &ppapszReq[1]);
1166 if (RT_SUCCESS(rc))
1167 {
1168 if (RTHttpHeaderListGet(pReq->hHdrLst, "Connection", RTSTR_MAX))
1169 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Insert the real value here. */
1170 }
1171 }
1172 break;
1173 } /* for (;;) */
1174
1175 /*
1176 * Cleanup.
1177 */
1178
1179 for (size_t i = 0; i < cFirstLine; i++)
1180 RTStrFree(ppapszFirstLine[i]);
1181 RTMemFree(ppapszFirstLine);
1182
1183 for (size_t i = 0; i < cReq; i++)
1184 RTStrFree(ppapszReq[i]);
1185 RTMemFree(ppapszReq);
1186
1187 if (RT_FAILURE(rc))
1188 {
1189 rtHttpServerReqFree(pReq);
1190 pReq = NULL;
1191 }
1192 else
1193 *ppReq = pReq;
1194
1195 return rc;
1196}
1197
1198/**
1199 * Main function for processing client requests.
1200 *
1201 * @returns VBox status code.
1202 * @param pClient Client to process request for.
1203 * @param pszReq Request string to parse and handle.
1204 * @param cbReq Size (in bytes) of request string.
1205 */
1206static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
1207{
1208 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
1209
1210 PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
1211 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
1212 if (RT_SUCCESS(rc))
1213 {
1214 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
1215
1216 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRequestBegin, pReq);
1217
1218 unsigned i = 0;
1219 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
1220 {
1221 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
1222 if (pReq->enmMethod == pMethodEntry->enmMethod)
1223 {
1224 /* Hand in request to method handler. */
1225 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
1226 if (RT_FAILURE(rcMethod))
1227 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
1228 break;
1229 }
1230 }
1231
1232 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRequestEnd, pReq);
1233
1234 if (i == RT_ELEMENTS(g_aMethodMap))
1235 enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
1236
1237 rtHttpServerReqFree(pReq);
1238 }
1239 else
1240 enmSts = RTHTTPSTATUS_BADREQUEST;
1241
1242 /* Make sure to return at least *something* to the client, to prevent hangs. */
1243 if (enmSts == RTHTTPSTATUS_INTERNAL_NOT_SET)
1244 enmSts = rtHttpServerRcToStatus(VERR_INTERNAL_ERROR);
1245
1246 int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
1247 if (RT_SUCCESS(rc))
1248 rc = rc2;
1249
1250 LogFlowFuncLeaveRC(rc);
1251 return rc;
1252}
1253
1254/**
1255 * Main loop for processing client requests.
1256 *
1257 * @returns VBox status code.
1258 * @param pClient Client to process requests for.
1259 * @param msTimeout Timeout to wait for reading data.
1260 * Gets renewed for a each reading round.
1261 */
1262static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient, RTMSINTERVAL msTimeout)
1263{
1264 int rc;
1265
1266 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
1267
1268 LogFlowFunc(("Client connected\n"));
1269
1270 /* Initialize client state. */
1271 pClient->State.msKeepAlive = 0;
1272
1273 RTMSINTERVAL cWaitMs = msTimeout;
1274 uint64_t tsLastReadMs = 0;
1275
1276 for (;;) /* For keep-alive handling. */
1277 {
1278 rc = RTTcpSelectOne(pClient->hSocket, cWaitMs);
1279 if (RT_FAILURE(rc))
1280 {
1281 Log2Func(("RTTcpSelectOne=%Rrc (cWaitMs=%RU64)\n", rc, cWaitMs));
1282 if (rc == VERR_TIMEOUT)
1283 {
1284 if (pClient->State.msKeepAlive) /* Keep alive handling needed? */
1285 {
1286 if (!tsLastReadMs)
1287 tsLastReadMs = RTTimeMilliTS();
1288 const uint64_t tsDeltaMs = pClient->State.msKeepAlive - (RTTimeMilliTS() - tsLastReadMs);
1289 Log2Func(("tsLastReadMs=%RU64, tsDeltaMs=%RU64\n", tsLastReadMs, tsDeltaMs));
1290 Log2Func(("Keep alive active (%RU32ms): %RU64ms remaining\n", pClient->State.msKeepAlive, tsDeltaMs));
1291 if ( tsDeltaMs > cWaitMs
1292 && tsDeltaMs < pClient->State.msKeepAlive)
1293 continue;
1294
1295 LogFunc(("Keep alive active: Client did not respond within %RU32ms, closing\n", pClient->State.msKeepAlive));
1296 rc = VINF_SUCCESS;
1297 break;
1298 }
1299 }
1300
1301 break;
1302 }
1303
1304 LogFlowFunc(("Reading client request ...\n"));
1305
1306 tsLastReadMs = RTTimeMilliTS();
1307 cWaitMs = 200; /* All consequtive waits do busy waiting for now. */
1308
1309 char *pszReq = szReq;
1310 size_t cbRead;
1311 size_t cbToRead = sizeof(szReq);
1312 size_t cbReadTotal = 0;
1313
1314 do
1315 {
1316 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
1317 if (RT_FAILURE(rc))
1318 break;
1319
1320 if (!cbRead)
1321 break;
1322
1323 /* Make sure to terminate string read so far. */
1324 pszReq[cbRead] = '\0';
1325
1326 /* End of request reached? */
1327 /** @todo BUGBUG Improve this. */
1328 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
1329 if (pszEOR)
1330 {
1331 cbReadTotal = pszEOR - pszReq;
1332 *pszEOR = '\0';
1333 break;
1334 }
1335
1336 AssertBreak(cbToRead >= cbRead);
1337 cbToRead -= cbRead;
1338 cbReadTotal += cbRead;
1339 AssertBreak(cbReadTotal <= sizeof(szReq));
1340 pszReq += cbRead;
1341
1342 } while (cbToRead);
1343
1344 Log2Func(("Read client request done (%zu bytes) -> rc=%Rrc\n", cbReadTotal, rc));
1345
1346 if ( RT_SUCCESS(rc)
1347 && cbReadTotal)
1348 {
1349 rtHttpServerLogProto(pClient, false /* fWrite */, szReq);
1350
1351 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
1352 }
1353
1354 break;
1355
1356 } /* for */
1357
1358 if (RT_FAILURE(rc))
1359 {
1360 switch (rc)
1361 {
1362 case VERR_NET_CONNECTION_RESET_BY_PEER:
1363 {
1364 LogFunc(("Client closed the connection\n"));
1365 rc = VINF_SUCCESS;
1366 break;
1367 }
1368
1369 default:
1370 LogFunc(("Client processing failed with %Rrc\n", rc));
1371 break;
1372 }
1373 }
1374
1375 LogFlowFuncLeaveRC(rc);
1376 return rc;
1377}
1378
1379/**
1380 * Per-client thread for serving the server's control connection.
1381 *
1382 * @returns VBox status code.
1383 * @param hSocket Socket handle to use for the control connection.
1384 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
1385 */
1386static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
1387{
1388 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
1389 RTHTTPSERVER_VALID_RETURN(pThis);
1390
1391 LogFlowFuncEnter();
1392
1393 RTHTTPSERVERCLIENT Client;
1394 RT_ZERO(Client);
1395
1396 Client.pServer = pThis;
1397 Client.hSocket = hSocket;
1398
1399 return rtHttpServerClientMain(&Client, RT_MS_30SEC /* Timeout */);
1400}
1401
1402RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
1403 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1404{
1405 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
1406 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
1407 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1408 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1409 /* pvUser is optional. */
1410
1411 int rc;
1412
1413 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
1414 if (pThis)
1415 {
1416 pThis->u32Magic = RTHTTPSERVER_MAGIC;
1417 pThis->Callbacks = *pCallbacks;
1418 pThis->pvUser = pvUser;
1419 pThis->cbUser = cbUser;
1420
1421 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
1422 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1423 if (RT_SUCCESS(rc))
1424 {
1425 *hHttpServer = (RTHTTPSERVER)pThis;
1426 }
1427 }
1428 else
1429 rc = VERR_NO_MEMORY;
1430
1431 return rc;
1432}
1433
1434RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
1435{
1436 if (hHttpServer == NIL_RTHTTPSERVER)
1437 return VINF_SUCCESS;
1438
1439 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
1440 RTHTTPSERVER_VALID_RETURN(pThis);
1441
1442 AssertPtr(pThis->pTCPServer);
1443
1444 int rc = VINF_SUCCESS;
1445
1446 PRTHTTPSERVERCALLBACKS pCallbacks = &pThis->Callbacks;
1447 if (pCallbacks->pfnDestroy)
1448 {
1449 RTHTTPCALLBACKDATA Data = { NULL /* pClient */, pThis->pvUser, pThis->cbUser };
1450 rc = pCallbacks->pfnDestroy(&Data);
1451 }
1452
1453 if (RT_SUCCESS(rc))
1454 {
1455 rc = RTTcpServerDestroy(pThis->pTCPServer);
1456 if (RT_SUCCESS(rc))
1457 {
1458 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
1459
1460 RTMemFree(pThis);
1461 }
1462 }
1463
1464 return rc;
1465}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette