VirtualBox

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

Last change on this file since 100315 was 100250, checked in by vboxsync, 22 months ago

IPRT/HttpServer: Emphasize logging group [SCM fix]. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.6 KB
Line 
1/* $Id: http-server.cpp 100250 2023-06-22 15:13:41Z 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 default:
692 break;
693 }
694
695 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
696 return RTHTTPSTATUS_INTERNALSERVERERROR;
697}
698
699
700/*********************************************************************************************************************************
701* Command Protocol Handlers *
702*********************************************************************************************************************************/
703
704/**
705 * Handler for the GET method.
706 *
707 * @returns VBox status code.
708 * @param pClient Client to handle GET method for.
709 * @param pReq Client request to handle.
710 */
711static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
712{
713 LogFlowFuncEnter();
714
715 int rc = VINF_SUCCESS;
716
717 /* If a low-level GET request handler is defined, call it and return. */
718 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
719
720 RTFSOBJINFO fsObj;
721 RT_ZERO(fsObj); /* Shut up MSVC. */
722
723 char *pszMIMEHint = NULL;
724
725 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, &pszMIMEHint);
726 if (RT_FAILURE(rc))
727 return rc;
728
729 void *pvHandle = NULL;
730 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
731
732 if (RT_SUCCESS(rc))
733 {
734 size_t cbBuf = _64K;
735 void *pvBuf = RTMemAlloc(cbBuf);
736 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
737
738 for (;;)
739 {
740 RTHTTPHEADERLIST HdrLst;
741 rc = RTHttpHeaderListInit(&HdrLst);
742 AssertRCReturn(rc, rc);
743
744 char szVal[16];
745
746 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
747 * of the body data for the directory listing. */
748
749 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
750 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
751 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
752 AssertRCBreak(rc);
753
754 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
755 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
756 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
757 AssertRCBreak(rc);
758
759 if (pszMIMEHint == NULL)
760 {
761 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
762 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
763 }
764 else
765 {
766 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK);
767 RTStrFree(pszMIMEHint);
768 pszMIMEHint = NULL;
769 }
770 AssertRCBreak(rc);
771
772 if (pClient->State.msKeepAlive)
773 {
774 /* If the client requested to keep alive the connection,
775 * always override this with 30s and report this back to the client. */
776 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Make this configurable. */
777#ifdef DEBUG_andy
778 pClient->State.msKeepAlive = 5000;
779#endif
780 cch = RTStrPrintf2(szVal, sizeof(szVal), "timeout=%RU64", pClient->State.msKeepAlive / RT_MS_1SEC); /** @todo No pipelining support here yet. */
781 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
782 rc = RTHttpHeaderListAdd(HdrLst, "Keep-Alive", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
783 AssertRCReturn(rc, rc);
784 }
785
786 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
787
788 RTHttpHeaderListDestroy(HdrLst);
789
790 if (rc == VERR_BROKEN_PIPE) /* Could happen on fast reloads. */
791 break;
792 AssertRCReturn(rc, rc);
793
794 size_t cbToRead = fsObj.cbObject;
795 size_t cbRead = 0; /* Shut up GCC. */
796 size_t cbWritten = 0; /* Ditto. */
797 while (cbToRead)
798 {
799 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
800 if (RT_FAILURE(rc))
801 break;
802 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
803 AssertBreak(cbToRead >= cbWritten);
804 cbToRead -= cbWritten;
805 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
806 {
807 rc = VINF_SUCCESS;
808 break;
809 }
810 AssertRCBreak(rc);
811 }
812
813 break;
814 } /* for (;;) */
815
816 RTMemFree(pvBuf);
817
818 int rc2 = rc; /* Save rc. */
819
820 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
821
822 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
823 rc = rc2;
824 }
825
826 LogFlowFuncLeaveRC(rc);
827 return rc;
828}
829
830/**
831 * Handler for the HEAD method.
832 *
833 * @returns VBox status code.
834 * @param pClient Client to handle HEAD method for.
835 * @param pReq Client request to handle.
836 */
837static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
838{
839 LogFlowFuncEnter();
840
841 /* If a low-level HEAD request handler is defined, call it and return. */
842 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
843
844 int rc = VINF_SUCCESS;
845
846 RTFSOBJINFO fsObj;
847 RT_ZERO(fsObj); /* Shut up MSVC. */
848
849 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
850 if (RT_SUCCESS(rc))
851 {
852 RTHTTPHEADERLIST HdrLst;
853 rc = RTHttpHeaderListInit(&HdrLst);
854 AssertRCReturn(rc, rc);
855
856 /*
857 * Note: A response to a HEAD request does not have a body.
858 * All entity headers below are assumed to describe the the response a similar GET
859 * request would return (but then with a body).
860 */
861 char szVal[16];
862
863 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
864 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
865 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
866 AssertRCReturn(rc, rc);
867
868 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
869 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
870 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
871 AssertRCReturn(rc, rc);
872
873 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
874 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
875 AssertRCReturn(rc, rc);
876
877 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
878 AssertRCReturn(rc, rc);
879
880 RTHttpHeaderListDestroy(HdrLst);
881 }
882
883 LogFlowFuncLeaveRC(rc);
884 return rc;
885}
886
887#ifdef IPRT_HTTP_WITH_WEBDAV
888/**
889 * Handler for the OPTIONS method.
890 *
891 * @returns VBox status code.
892 * @param pClient Client to handle OPTIONS method for.
893 * @param pReq Client request to handle.
894 */
895static DECLCALLBACK(int) rtHttpServerHandleOPTIONS(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
896{
897 LogFlowFuncEnter();
898
899 RT_NOREF(pReq);
900
901 int rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, NULL /* pHdrLst */);
902
903 LogFlowFuncLeaveRC(rc);
904 return rc;
905}
906
907/**
908 * Handler for the PROPFIND (WebDAV) method.
909 *
910 * @returns VBox status code.
911 * @param pClient Client to handle PROPFIND method for.
912 * @param pReq Client request to handle.
913 */
914static DECLCALLBACK(int) rtHttpServerHandlePROPFIND(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
915{
916 LogFlowFuncEnter();
917
918 int rc = VINF_SUCCESS;
919
920 /* If a low-level GET request handler is defined, call it and return. */
921 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
922
923 RTFSOBJINFO fsObj;
924 RT_ZERO(fsObj); /* Shut up MSVC. */
925
926 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
927 if (RT_FAILURE(rc))
928 return rc;
929
930 void *pvHandle = NULL;
931 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
932
933 if (RT_SUCCESS(rc))
934 {
935 size_t cbBuf = _64K;
936 void *pvBuf = RTMemAlloc(cbBuf);
937 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
938
939 for (;;)
940 {
941 RTHTTPHEADERLIST HdrLst;
942 rc = RTHttpHeaderListInit(&HdrLst);
943 AssertRCReturn(rc, rc);
944
945 char szVal[16];
946
947 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", "text/xml; charset=utf-8", strlen("text/xml; charset=utf-8"), RTHTTPHEADERLISTADD_F_BACK);
948 AssertRCBreak(rc);
949
950 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
951 * of the body data for the directory listing. */
952
953 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
954 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
955 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
956 AssertRCBreak(rc);
957
958 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_MULTISTATUS, &HdrLst);
959 AssertRCReturn(rc, rc);
960
961 RTHttpHeaderListDestroy(HdrLst);
962
963 size_t cbToRead = fsObj.cbObject;
964 size_t cbRead = 0; /* Shut up GCC. */
965 size_t cbWritten = 0; /* Ditto. */
966 while (cbToRead)
967 {
968 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
969 if (RT_FAILURE(rc))
970 break;
971 //rtHttpServerLogProto(pClient, true /* fWrite */, (const char *)pvBuf);
972 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
973 AssertBreak(cbToRead >= cbWritten);
974 cbToRead -= cbWritten;
975 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
976 {
977 rc = VINF_SUCCESS;
978 break;
979 }
980 AssertRCBreak(rc);
981 }
982
983 break;
984 } /* for (;;) */
985
986 RTMemFree(pvBuf);
987
988 int rc2 = rc; /* Save rc. */
989
990 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
991
992 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
993 rc = rc2;
994 }
995
996 LogFlowFuncLeaveRC(rc);
997 return rc;
998}
999#endif /* IPRT_HTTP_WITH_WEBDAV */
1000
1001/**
1002 * Validates if a given path is valid or not.
1003 *
1004 * @returns \c true if path is valid, or \c false if not.
1005 * @param pszPath Path to check.
1006 * @param fIsAbsolute Whether the path to check is an absolute path or not.
1007 */
1008static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
1009{
1010 if (!pszPath)
1011 return false;
1012
1013 bool fIsValid = strlen(pszPath)
1014 && RTStrIsValidEncoding(pszPath)
1015 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
1016 if ( fIsValid
1017 && fIsAbsolute)
1018 {
1019 RTFSOBJINFO objInfo;
1020 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
1021 if (RT_SUCCESS(rc2))
1022 {
1023 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
1024 || RTFS_IS_FILE(objInfo.Attr.fMode);
1025
1026 /* No symlinks and other stuff allowed. */
1027 }
1028 else
1029 fIsValid = false;
1030 }
1031
1032 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
1033 return fIsValid;
1034
1035}
1036
1037/**
1038 * Parses headers and sets (replaces) a given header list.
1039 *
1040 * @returns VBox status code.
1041 * @param hList Header list to fill parsed headers in.
1042 * @param cStrings Number of strings to parse for \a papszStrings.
1043 * @param papszStrings Array of strings to parse.
1044 */
1045static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, size_t cStrings, char **papszStrings)
1046{
1047 /* Nothing to parse left? Bail out early. */
1048 if ( !cStrings
1049 || !papszStrings)
1050 return VINF_SUCCESS;
1051
1052#ifdef LOG_ENABLED
1053 for (size_t i = 0; i < cStrings; i++)
1054 LogFlowFunc(("Header: %s\n", papszStrings[i]));
1055#endif
1056
1057 int rc = RTHttpHeaderListSet(hList, cStrings, papszStrings);
1058
1059 LogFlowFunc(("rc=%Rrc, cHeaders=%zu\n", rc, cStrings));
1060 return rc;
1061}
1062
1063/**
1064 * Main function for parsing and allocating a client request.
1065 *
1066 * See: https://tools.ietf.org/html/rfc2616#section-2.2
1067 *
1068 * @returns VBox status code.
1069 * @param pClient Client to parse request from.
1070 * @param pszReq Request string with headers to parse.
1071 * @param cbReq Size (in bytes) of request string to parse.
1072 * @param ppReq Where to store the allocated client request on success.
1073 * Needs to be free'd via rtHttpServerReqFree().
1074 */
1075static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, const char *pszReq, size_t cbReq,
1076 PRTHTTPSERVERREQ *ppReq)
1077{
1078 RT_NOREF(pClient);
1079
1080 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
1081 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
1082
1083 /* We only support UTF-8 charset for now. */
1084 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
1085
1086 char **ppapszReq = NULL;
1087 size_t cReq = 0;
1088 int rc = RTStrSplit(pszReq, cbReq, RTHTTPSERVER_HTTP11_EOL_STR, &ppapszReq, &cReq);
1089 if (RT_FAILURE(rc))
1090 return rc;
1091
1092 if (!cReq)
1093 return VERR_INVALID_PARAMETER;
1094
1095#ifdef LOG_ENABLED
1096 for (size_t i = 0; i < cReq; i++)
1097 LogFlowFunc(("%s\n", ppapszReq[i]));
1098#endif
1099
1100 PRTHTTPSERVERREQ pReq = NULL;
1101
1102 char **ppapszFirstLine = NULL;
1103 size_t cFirstLine = 0;
1104 rc = RTStrSplit(ppapszReq[0], strlen(ppapszReq[0]), " ", &ppapszFirstLine, &cFirstLine);
1105 if (RT_SUCCESS(rc))
1106 {
1107 if (cFirstLine < 3) /* At leat the method, path and version has to be present. */
1108 rc = VERR_INVALID_PARAMETER;
1109 }
1110
1111 while (RT_SUCCESS(rc)) /* To use break. */
1112 {
1113 pReq = rtHttpServerReqAlloc();
1114 AssertPtrBreakStmt(pReq, rc = VERR_NO_MEMORY);
1115
1116 /*
1117 * Parse method to use. Method names are case sensitive.
1118 */
1119 const char *pszMethod = ppapszFirstLine[0];
1120 if (!RTStrCmp(pszMethod, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
1121 else if (!RTStrCmp(pszMethod, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
1122#ifdef IPRT_HTTP_WITH_WEBDAV
1123 else if (!RTStrCmp(pszMethod, "OPTIONS")) pReq->enmMethod = RTHTTPMETHOD_OPTIONS;
1124 else if (!RTStrCmp(pszMethod, "PROPFIND")) pReq->enmMethod = RTHTTPMETHOD_PROPFIND;
1125#endif
1126 else
1127 {
1128 rc = VERR_NOT_SUPPORTED;
1129 break;
1130 }
1131
1132 /*
1133 * Parse requested path.
1134 */
1135 /** @todo Do URL unescaping here. */
1136 const char *pszPath = ppapszFirstLine[1];
1137 if (!rtHttpServerPathIsValid(pszPath, false /* fIsAbsolute */))
1138 {
1139 rc = VERR_PATH_NOT_FOUND;
1140 break;
1141 }
1142
1143 pReq->pszUrl = RTStrDup(pszPath);
1144
1145 /*
1146 * Parse HTTP version to use.
1147 * We're picky here: Only HTTP 1.1 is supported by now.
1148 */
1149 const char *pszVer = ppapszFirstLine[2];
1150 if (RTStrCmp(pszVer, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
1151 {
1152 rc = VERR_NOT_SUPPORTED;
1153 break;
1154 }
1155
1156 /** @todo Anything else needed for the first line here? */
1157
1158 /*
1159 * Process headers, if any.
1160 */
1161 if (cReq > 1)
1162 {
1163 rc = rtHttpServerParseHeaders(pReq->hHdrLst, cReq - 1, &ppapszReq[1]);
1164 if (RT_SUCCESS(rc))
1165 {
1166 if (RTHttpHeaderListGet(pReq->hHdrLst, "Connection", RTSTR_MAX))
1167 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Insert the real value here. */
1168 }
1169 }
1170 break;
1171 } /* for (;;) */
1172
1173 /*
1174 * Cleanup.
1175 */
1176
1177 for (size_t i = 0; i < cFirstLine; i++)
1178 RTStrFree(ppapszFirstLine[i]);
1179 RTMemFree(ppapszFirstLine);
1180
1181 for (size_t i = 0; i < cReq; i++)
1182 RTStrFree(ppapszReq[i]);
1183 RTMemFree(ppapszReq);
1184
1185 if (RT_FAILURE(rc))
1186 {
1187 rtHttpServerReqFree(pReq);
1188 pReq = NULL;
1189 }
1190 else
1191 *ppReq = pReq;
1192
1193 return rc;
1194}
1195
1196/**
1197 * Main function for processing client requests.
1198 *
1199 * @returns VBox status code.
1200 * @param pClient Client to process request for.
1201 * @param pszReq Request string to parse and handle.
1202 * @param cbReq Size (in bytes) of request string.
1203 */
1204static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
1205{
1206 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
1207
1208 PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
1209 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
1210 if (RT_SUCCESS(rc))
1211 {
1212 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
1213
1214 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRequestBegin, pReq);
1215
1216 unsigned i = 0;
1217 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
1218 {
1219 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
1220 if (pReq->enmMethod == pMethodEntry->enmMethod)
1221 {
1222 /* Hand in request to method handler. */
1223 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
1224 if (RT_FAILURE(rcMethod))
1225 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
1226
1227 enmSts = rtHttpServerRcToStatus(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 = RTHTTPSTATUS_INTERNALSERVERERROR;
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 */
1260static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient)
1261{
1262 int rc;
1263
1264 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
1265
1266 LogFlowFunc(("Client connected\n"));
1267
1268 /* Initialize client state. */
1269 pClient->State.msKeepAlive = 0;
1270
1271 RTMSINTERVAL cWaitMs = RT_INDEFINITE_WAIT; /* The first wait always waits indefinitely. */
1272 uint64_t tsLastReadMs = 0;
1273
1274 for (;;)
1275 {
1276 rc = RTTcpSelectOne(pClient->hSocket, cWaitMs);
1277 if (RT_FAILURE(rc))
1278 {
1279 Log2Func(("RTTcpSelectOne=%Rrc (cWaitMs=%RU64)\n", rc, cWaitMs));
1280 if (rc == VERR_TIMEOUT)
1281 {
1282 if (pClient->State.msKeepAlive) /* Keep alive handling needed? */
1283 {
1284 if (!tsLastReadMs)
1285 tsLastReadMs = RTTimeMilliTS();
1286 const uint64_t tsDeltaMs = pClient->State.msKeepAlive - (RTTimeMilliTS() - tsLastReadMs);
1287 Log2Func(("tsLastReadMs=%RU64, tsDeltaMs=%RU64\n", tsLastReadMs, tsDeltaMs));
1288 Log2Func(("Keep alive active (%RU32ms): %RU64ms remaining\n", pClient->State.msKeepAlive, tsDeltaMs));
1289 if ( tsDeltaMs > cWaitMs
1290 && tsDeltaMs < pClient->State.msKeepAlive)
1291 continue;
1292
1293 LogFunc(("Keep alive active: Client did not respond within %RU32ms, closing\n", pClient->State.msKeepAlive));
1294 rc = VINF_SUCCESS;
1295 break;
1296 }
1297 }
1298
1299 break;
1300 }
1301
1302 LogFlowFunc(("Reading client request ...\n"));
1303
1304 tsLastReadMs = RTTimeMilliTS();
1305 cWaitMs = 200; /* All consequtive waits do busy waiting for now. */
1306
1307 char *pszReq = szReq;
1308 size_t cbRead;
1309 size_t cbToRead = sizeof(szReq);
1310 size_t cbReadTotal = 0;
1311
1312 do
1313 {
1314 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
1315 if (RT_FAILURE(rc))
1316 break;
1317
1318 if (!cbRead)
1319 break;
1320
1321 /* Make sure to terminate string read so far. */
1322 pszReq[cbRead] = '\0';
1323
1324 /* End of request reached? */
1325 /** @todo BUGBUG Improve this. */
1326 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
1327 if (pszEOR)
1328 {
1329 cbReadTotal = pszEOR - pszReq;
1330 *pszEOR = '\0';
1331 break;
1332 }
1333
1334 AssertBreak(cbToRead >= cbRead);
1335 cbToRead -= cbRead;
1336 cbReadTotal += cbRead;
1337 AssertBreak(cbReadTotal <= sizeof(szReq));
1338 pszReq += cbRead;
1339
1340 } while (cbToRead);
1341
1342 Log2Func(("Read client request done (%zu bytes) -> rc=%Rrc\n", cbReadTotal, rc));
1343
1344 if ( RT_SUCCESS(rc)
1345 && cbReadTotal)
1346 {
1347 rtHttpServerLogProto(pClient, false /* fWrite */, szReq);
1348
1349 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
1350 }
1351 else
1352 break;
1353
1354 } /* for */
1355
1356 if (RT_FAILURE(rc))
1357 {
1358 switch (rc)
1359 {
1360 case VERR_NET_CONNECTION_RESET_BY_PEER:
1361 {
1362 LogFunc(("Client closed the connection\n"));
1363 rc = VINF_SUCCESS;
1364 break;
1365 }
1366
1367 default:
1368 LogFunc(("Client processing failed with %Rrc\n", rc));
1369 break;
1370 }
1371 }
1372
1373 LogFlowFuncLeaveRC(rc);
1374 return rc;
1375}
1376
1377/**
1378 * Per-client thread for serving the server's control connection.
1379 *
1380 * @returns VBox status code.
1381 * @param hSocket Socket handle to use for the control connection.
1382 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
1383 */
1384static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
1385{
1386 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
1387 RTHTTPSERVER_VALID_RETURN(pThis);
1388
1389 LogFlowFuncEnter();
1390
1391 RTHTTPSERVERCLIENT Client;
1392 RT_ZERO(Client);
1393
1394 Client.pServer = pThis;
1395 Client.hSocket = hSocket;
1396
1397 return rtHttpServerClientMain(&Client);
1398}
1399
1400RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
1401 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1402{
1403 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
1404 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
1405 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1406 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1407 /* pvUser is optional. */
1408
1409 int rc;
1410
1411 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
1412 if (pThis)
1413 {
1414 pThis->u32Magic = RTHTTPSERVER_MAGIC;
1415 pThis->Callbacks = *pCallbacks;
1416 pThis->pvUser = pvUser;
1417 pThis->cbUser = cbUser;
1418
1419 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
1420 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1421 if (RT_SUCCESS(rc))
1422 {
1423 *hHttpServer = (RTHTTPSERVER)pThis;
1424 }
1425 }
1426 else
1427 rc = VERR_NO_MEMORY;
1428
1429 return rc;
1430}
1431
1432RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
1433{
1434 if (hHttpServer == NIL_RTHTTPSERVER)
1435 return VINF_SUCCESS;
1436
1437 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
1438 RTHTTPSERVER_VALID_RETURN(pThis);
1439
1440 AssertPtr(pThis->pTCPServer);
1441
1442 int rc = VINF_SUCCESS;
1443
1444 PRTHTTPSERVERCALLBACKS pCallbacks = &pThis->Callbacks;
1445 if (pCallbacks->pfnDestroy)
1446 {
1447 RTHTTPCALLBACKDATA Data = { NULL /* pClient */, pThis->pvUser, pThis->cbUser };
1448 rc = pCallbacks->pfnDestroy(&Data);
1449 }
1450
1451 if (RT_SUCCESS(rc))
1452 {
1453 rc = RTTcpServerDestroy(pThis->pTCPServer);
1454 if (RT_SUCCESS(rc))
1455 {
1456 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
1457
1458 RTMemFree(pThis);
1459 }
1460 }
1461
1462 return rc;
1463}
Note: See TracBrowser for help on using the repository browser.

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