VirtualBox

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

Last change on this file since 103914 was 103118, checked in by vboxsync, 10 months ago

Runtime/r3/http-server.cpp: Fix possible memory leak, small other fixes, bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 48.0 KB
Line 
1/* $Id: http-server.cpp 103118 2024-01-30 09:01:39Z 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 pReq->hHdrLst = NIL_RTHTTPHEADERLIST;
426
427 rtHttpServerBodyDestroy(&pReq->Body);
428
429 RTMemFree(pReq);
430 pReq = NULL;
431}
432
433/**
434 * Initializes a HTTP server response with an allocated body size.
435 *
436 * @returns VBox status code.
437 * @param pResp HTTP server response to initialize.
438 * @param cbBody Body size (in bytes) to allocate.
439 */
440RTR3DECL(int) RTHttpServerResponseInitEx(PRTHTTPSERVERRESP pResp, size_t cbBody)
441{
442 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
443
444 int rc = RTHttpHeaderListInit(&pResp->hHdrLst);
445 AssertRCReturn(rc, rc);
446
447 rc = rtHttpServerBodyInit(&pResp->Body, cbBody);
448
449 return rc;
450}
451
452/**
453 * Initializes a HTTP server response.
454 *
455 * @returns VBox status code.
456 * @param pResp HTTP server response to initialize.
457 */
458RTR3DECL(int) RTHttpServerResponseInit(PRTHTTPSERVERRESP pResp)
459{
460 return RTHttpServerResponseInitEx(pResp, 0 /* cbBody */);
461}
462
463/**
464 * Destroys a formerly initialized HTTP server response.
465 *
466 * @param pResp Pointer to HTTP server response to destroy.
467 */
468RTR3DECL(void) RTHttpServerResponseDestroy(PRTHTTPSERVERRESP pResp)
469{
470 if (!pResp)
471 return;
472
473 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
474
475 RTHttpHeaderListDestroy(pResp->hHdrLst);
476 pResp->hHdrLst = NIL_RTHTTPHEADERLIST;
477
478 rtHttpServerBodyDestroy(&pResp->Body);
479}
480
481
482/*********************************************************************************************************************************
483* Protocol Functions *
484*********************************************************************************************************************************/
485
486/**
487 * Logs the HTTP protocol communication to the debug logger (2).
488 *
489 * @param pClient Client to log communication for.
490 * @param fWrite Whether the server is writing (to client) or reading (from client).
491 * @param pszData Actual protocol communication data to log.
492 */
493static void rtHttpServerLogProto(PRTHTTPSERVERCLIENT pClient, bool fWrite, const char *pszData)
494{
495 RT_NOREF(pClient);
496
497#ifdef LOG_ENABLED
498 if (!pszData) /* Nothing to log? Bail out. */
499 return;
500
501 char **ppapszStrings;
502 size_t cStrings;
503 int rc2 = RTStrSplit(pszData, strlen(pszData) + 1 /* Must include terminator */,
504 RTHTTPSERVER_HTTP11_EOL_STR, &ppapszStrings, &cStrings);
505 if (RT_SUCCESS(rc2))
506 {
507 for (size_t i = 0; i < cStrings; i++)
508 {
509 Log2(("%s %s\n", fWrite ? ">" : "<", ppapszStrings[i]));
510 RTStrFree(ppapszStrings[i]);
511 }
512
513 RTMemFree(ppapszStrings);
514 }
515#else
516 RT_NOREF(fWrite, pszData);
517#endif
518}
519
520/**
521 * Writes HTTP protocol communication data to a connected client.
522 *
523 * @returns VBox status code.
524 * @param pClient Client to write data to.
525 * @param pszData Data to write. Must be zero-terminated.
526 */
527static int rtHttpServerWriteProto(PRTHTTPSERVERCLIENT pClient, const char *pszData)
528{
529 rtHttpServerLogProto(pClient, true /* fWrite */, pszData);
530 return RTTcpWrite(pClient->hSocket, pszData, strlen(pszData));
531}
532
533/**
534 * Main function for sending a response back to the client.
535 *
536 * @returns VBox status code.
537 * @param pClient Client to reply to.
538 * @param enmSts Status code to send.
539 */
540static int rtHttpServerSendResponse(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
541{
542 char *pszResp;
543 int rc = RTStrAPrintf(&pszResp,
544 "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
545 AssertRCReturn(rc, rc);
546 rc = rtHttpServerWriteProto(pClient, pszResp);
547 RTStrFree(pszResp);
548
549 LogFlowFuncLeaveRC(rc);
550 return rc;
551}
552
553/**
554 * Main function for sending response headers back to the client.
555 *
556 * @returns VBox status code.
557 * @param pClient Client to reply to.
558 * @param pHdrLst Header list to send. Optional and can be NULL.
559 */
560static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient, PRTHTTPHEADERLIST pHdrLst)
561{
562 RTHTTPHEADERLIST HdrLst;
563 int rc = RTHttpHeaderListInit(&HdrLst);
564 AssertRCReturn(rc, rc);
565
566#ifdef DEBUG
567 /* Include a timestamp when running a debug build. */
568 RTTIMESPEC tsNow;
569 char szTS[64];
570 RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
571 rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
572 AssertRCReturn(rc, rc);
573#endif
574
575 /* Note: Deliberately don't include the VBox version due to security reasons. */
576 rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
577 AssertRCReturn(rc, rc);
578
579#ifdef IPRT_HTTP_WITH_WEBDAV
580 rc = RTHttpHeaderListAdd(HdrLst, "Allow", "GET, HEAD, PROPFIND", strlen("GET, HEAD, PROPFIND"), RTHTTPHEADERLISTADD_F_BACK);
581 AssertRCReturn(rc, rc);
582 rc = RTHttpHeaderListAdd(HdrLst, "DAV", "1", strlen("1"), RTHTTPHEADERLISTADD_F_BACK); /* Note: v1 is sufficient for read-only access. */
583 AssertRCReturn(rc, rc);
584#endif
585
586 char *pszHdr = NULL;
587
588 size_t i = 0;
589 const char *pszEntry;
590 while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
591 {
592 rc = RTStrAAppend(&pszHdr, pszEntry);
593 AssertRCBreak(rc);
594 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
595 AssertRCBreak(rc);
596 }
597
598 /* Append optional headers, if any. */
599 if (pHdrLst)
600 {
601 i = 0;
602 while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
603 {
604 rc = RTStrAAppend(&pszHdr, pszEntry);
605 AssertRCBreak(rc);
606 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
607 AssertRCBreak(rc);
608 }
609 }
610
611 if (RT_SUCCESS(rc))
612 {
613 /* Append trailing EOL. */
614 rc = RTStrAAppend(&pszHdr, RTHTTPSERVER_HTTP11_EOL_STR);
615 if (RT_SUCCESS(rc))
616 rc = rtHttpServerWriteProto(pClient, pszHdr);
617 }
618
619 RTStrFree(pszHdr);
620
621 RTHttpHeaderListDestroy(HdrLst);
622
623 LogFlowFunc(("rc=%Rrc\n", rc));
624 return rc;
625}
626
627/**
628 * Replies with (three digit) response status back to the client, extended version.
629 *
630 * @returns VBox status code.
631 * @param pClient Client to reply to.
632 * @param enmSts Status code to send.
633 * @param pHdrLst Header list to send. Optional and can be NULL.
634 */
635static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
636{
637 int rc = rtHttpServerSendResponse(pClient, enmSts);
638 if (RT_SUCCESS(rc))
639 rc = rtHttpServerSendResponseHdrEx(pClient, pHdrLst);
640
641 return rc;
642}
643
644/**
645 * Replies with (three digit) response status back to the client.
646 *
647 * @returns VBox status code.
648 * @param pClient Client to reply to.
649 * @param enmSts Status code to send.
650 */
651static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
652{
653 return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
654}
655
656/**
657 * Sends a chunk of the response body to the client.
658 *
659 * @returns VBox status code.
660 * @param pClient Client to send body to.
661 * @param pvBuf Data buffer to send.
662 * @param cbBuf Size (in bytes) of data buffer to send.
663 * @param pcbSent Where to store the sent bytes. Optional and can be NULL.
664 */
665static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
666{
667 int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
668 if ( RT_SUCCESS(rc)
669 && pcbSent)
670 *pcbSent = cbBuf;
671
672 return rc;
673}
674
675/**
676 * Resolves a VBox status code to a HTTP status code.
677 *
678 * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve.
679 * @param rc VBox status code to resolve.
680 */
681static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
682{
683 switch (rc)
684 {
685 case VINF_SUCCESS: return RTHTTPSTATUS_OK;
686 case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
687 case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
688 case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
689 case VERR_NOT_SUPPORTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
690 case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
691 case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
692 case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
693 case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
694 case VERR_INTERNAL_ERROR: return RTHTTPSTATUS_INTERNALSERVERERROR;
695 default:
696 break;
697 }
698
699 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
700 return RTHTTPSTATUS_INTERNALSERVERERROR;
701}
702
703
704/*********************************************************************************************************************************
705* Command Protocol Handlers *
706*********************************************************************************************************************************/
707
708/**
709 * Handler for the GET method.
710 *
711 * @returns VBox status code.
712 * @param pClient Client to handle GET method for.
713 * @param pReq Client request to handle.
714 */
715static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
716{
717 LogFlowFuncEnter();
718
719 /* If a low-level GET request handler is defined, call it and return. */
720 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
721
722 RTFSOBJINFO fsObj;
723 RT_ZERO(fsObj); /* Shut up MSVC. */
724
725 char *pszMIMEHint = NULL;
726
727 RTHTTPSTATUS enmStsResponse = RTHTTPSTATUS_OK;
728
729 int rc = VINF_SUCCESS;
730
731 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, &pszMIMEHint);
732 if (RT_FAILURE(rc))
733 enmStsResponse = rtHttpServerRcToStatus(rc);
734
735 void *pvHandle = NULL;
736 if (RT_SUCCESS(rc)) /* Only call open if querying information above succeeded. */
737 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
738
739 size_t cbBuf = _64K;
740 void *pvBuf = RTMemAlloc(cbBuf);
741 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
742
743 for (;;)
744 {
745 RTHTTPHEADERLIST HdrLst;
746 rc = RTHttpHeaderListInit(&HdrLst);
747 AssertRCBreak(rc);
748
749 char szVal[16];
750
751 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
752 * of the body data for the directory listing. */
753
754 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
755 AssertBreakStmt(cch, rc = VERR_BUFFER_OVERFLOW);
756 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
757 AssertRCBreak(rc);
758
759 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
760 AssertBreakStmt(cch, rc = VERR_BUFFER_OVERFLOW);
761 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
762 AssertRCBreak(rc);
763
764 if (pszMIMEHint == NULL)
765 {
766 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
767 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
768 }
769 else
770 {
771 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK);
772 RTStrFree(pszMIMEHint);
773 pszMIMEHint = NULL;
774 }
775 AssertRCBreak(rc);
776
777 if (pClient->State.msKeepAlive)
778 {
779 /* If the client requested to keep alive the connection,
780 * always override this with 30s and report this back to the client. */
781 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Make this configurable. */
782#ifdef DEBUG_andy
783 pClient->State.msKeepAlive = 5000;
784#endif
785 cch = RTStrPrintf2(szVal, sizeof(szVal), "timeout=%RU64", pClient->State.msKeepAlive / RT_MS_1SEC); /** @todo No pipelining support here yet. */
786 AssertBreakStmt(cch, rc = VERR_BUFFER_OVERFLOW);
787 rc = RTHttpHeaderListAdd(HdrLst, "Keep-Alive", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
788 AssertRCBreak(rc);
789 }
790
791 rc = rtHttpServerSendResponseEx(pClient, enmStsResponse, &HdrLst);
792
793 RTHttpHeaderListDestroy(HdrLst);
794 HdrLst = NIL_RTHTTPHEADERLIST;
795
796 if (rc == VERR_BROKEN_PIPE) /* Could happen on fast reloads. */
797 break;
798 AssertRCBreak(rc);
799
800 size_t cbToRead = fsObj.cbObject;
801 size_t cbRead = 0; /* Shut up GCC. */
802 size_t cbWritten = 0; /* Ditto. */
803 while (cbToRead)
804 {
805 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
806 if (RT_FAILURE(rc))
807 break;
808 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
809 if (RT_FAILURE(rc))
810 break;
811 AssertBreak(cbToRead >= cbWritten);
812 cbToRead -= cbWritten;
813#if 0 /* Disabled, VERR_BROKEN_PIPE happens too often with GVFS 1.50. */
814 AssertMsgRCBreak(rc, ("rc=%Rrc, cbRead=%zu, cbToRead=%zu, cbWritten=%zu\n", rc, cbRead, cbToRead, cbWritten));
815#endif
816 }
817
818 break;
819 } /* for (;;) */
820
821 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
822 rc = VINF_SUCCESS;
823
824 RTMemFree(pvBuf);
825
826 int rc2 = rc; /* Save rc. */
827
828 if (pvHandle)
829 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
830
831 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
832 rc = rc2;
833
834 LogFlowFuncLeaveRC(rc);
835 return rc;
836}
837
838/**
839 * Handler for the HEAD method.
840 *
841 * @returns VBox status code.
842 * @param pClient Client to handle HEAD method for.
843 * @param pReq Client request to handle.
844 */
845static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
846{
847 LogFlowFuncEnter();
848
849 /* If a low-level HEAD request handler is defined, call it and return. */
850 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
851
852 int rc = VINF_SUCCESS;
853
854 RTFSOBJINFO fsObj;
855 RT_ZERO(fsObj); /* Shut up MSVC. */
856
857 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
858 if (RT_SUCCESS(rc))
859 {
860 RTHTTPHEADERLIST HdrLst;
861 rc = RTHttpHeaderListInit(&HdrLst);
862 AssertRCReturn(rc, rc);
863
864 /*
865 * Note: A response to a HEAD request does not have a body.
866 * All entity headers below are assumed to describe the the response a similar GET
867 * request would return (but then with a body).
868 */
869 char szVal[16];
870
871 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
872 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
873 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
874 AssertRCReturn(rc, rc);
875
876 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
877 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
878 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
879 AssertRCReturn(rc, rc);
880
881 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
882 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
883 AssertRCReturn(rc, rc);
884
885 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
886 AssertRCReturn(rc, rc);
887
888 RTHttpHeaderListDestroy(HdrLst);
889 HdrLst = NIL_RTHTTPHEADERLIST;
890 }
891
892 LogFlowFuncLeaveRC(rc);
893 return rc;
894}
895
896#ifdef IPRT_HTTP_WITH_WEBDAV
897/**
898 * Handler for the OPTIONS method.
899 *
900 * @returns VBox status code.
901 * @param pClient Client to handle OPTIONS method for.
902 * @param pReq Client request to handle.
903 */
904static DECLCALLBACK(int) rtHttpServerHandleOPTIONS(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
905{
906 LogFlowFuncEnter();
907
908 RT_NOREF(pReq);
909
910 int rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, NULL /* pHdrLst */);
911
912 LogFlowFuncLeaveRC(rc);
913 return rc;
914}
915
916/**
917 * Handler for the PROPFIND (WebDAV) method.
918 *
919 * @returns VBox status code.
920 * @param pClient Client to handle PROPFIND method for.
921 * @param pReq Client request to handle.
922 */
923static DECLCALLBACK(int) rtHttpServerHandlePROPFIND(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
924{
925 LogFlowFuncEnter();
926
927 int rc = VINF_SUCCESS;
928
929 /* If a low-level GET request handler is defined, call it and return. */
930 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
931
932 RTFSOBJINFO fsObj;
933 RT_ZERO(fsObj); /* Shut up MSVC. */
934
935 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq, &fsObj, NULL /* pszMIMEHint */);
936 if (RT_FAILURE(rc))
937 return rc;
938
939 void *pvHandle = NULL;
940 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq, &pvHandle);
941
942 if (RT_SUCCESS(rc))
943 {
944 size_t cbBuf = _64K;
945 void *pvBuf = RTMemAlloc(cbBuf);
946 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
947
948 for (;;)
949 {
950 RTHTTPHEADERLIST HdrLst;
951 rc = RTHttpHeaderListInit(&HdrLst);
952 AssertRCReturn(rc, rc);
953
954 char szVal[16];
955
956 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", "text/xml; charset=utf-8", strlen("text/xml; charset=utf-8"), RTHTTPHEADERLISTADD_F_BACK);
957 AssertRCBreak(rc);
958
959 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
960 * of the body data for the directory listing. */
961
962 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
963 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
964 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
965 AssertRCBreak(rc);
966
967 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_MULTISTATUS, &HdrLst);
968 AssertRCReturn(rc, rc);
969
970 RTHttpHeaderListDestroy(HdrLst);
971 HdrLst = NIL_RTHTTPHEADERLIST;
972
973 size_t cbToRead = fsObj.cbObject;
974 size_t cbRead = 0; /* Shut up GCC. */
975 size_t cbWritten = 0; /* Ditto. */
976 while (cbToRead)
977 {
978 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pReq, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
979 if (RT_FAILURE(rc))
980 break;
981 //rtHttpServerLogProto(pClient, true /* fWrite */, (const char *)pvBuf);
982 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
983 AssertBreak(cbToRead >= cbWritten);
984 cbToRead -= cbWritten;
985 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
986 {
987 rc = VINF_SUCCESS;
988 break;
989 }
990 AssertRCBreak(rc);
991 }
992
993 break;
994 } /* for (;;) */
995
996 RTMemFree(pvBuf);
997
998 int rc2 = rc; /* Save rc. */
999
1000 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pReq, pvHandle);
1001
1002 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
1003 rc = rc2;
1004 }
1005
1006 LogFlowFuncLeaveRC(rc);
1007 return rc;
1008}
1009#endif /* IPRT_HTTP_WITH_WEBDAV */
1010
1011/**
1012 * Validates if a given path is valid or not.
1013 *
1014 * @returns \c true if path is valid, or \c false if not.
1015 * @param pszPath Path to check.
1016 * @param fIsAbsolute Whether the path to check is an absolute path or not.
1017 */
1018static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
1019{
1020 if (!pszPath)
1021 return false;
1022
1023 bool fIsValid = strlen(pszPath)
1024 && RTStrIsValidEncoding(pszPath)
1025 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
1026 if ( fIsValid
1027 && fIsAbsolute)
1028 {
1029 RTFSOBJINFO objInfo;
1030 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
1031 if (RT_SUCCESS(rc2))
1032 {
1033 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
1034 || RTFS_IS_FILE(objInfo.Attr.fMode);
1035
1036 /* No symlinks and other stuff allowed. */
1037 }
1038 else
1039 fIsValid = false;
1040 }
1041
1042 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
1043 return fIsValid;
1044
1045}
1046
1047/**
1048 * Parses headers and sets (replaces) a given header list.
1049 *
1050 * @returns VBox status code.
1051 * @param hList Header list to fill parsed headers in.
1052 * @param cStrings Number of strings to parse for \a papszStrings.
1053 * @param papszStrings Array of strings to parse.
1054 */
1055static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, size_t cStrings, char **papszStrings)
1056{
1057 /* Nothing to parse left? Bail out early. */
1058 if ( !cStrings
1059 || !papszStrings)
1060 return VINF_SUCCESS;
1061
1062#ifdef LOG_ENABLED
1063 for (size_t i = 0; i < cStrings; i++)
1064 LogFlowFunc(("Header: %s\n", papszStrings[i]));
1065#endif
1066
1067 int rc = RTHttpHeaderListSet(hList, cStrings, papszStrings);
1068
1069 LogFlowFunc(("rc=%Rrc, cHeaders=%zu\n", rc, cStrings));
1070 return rc;
1071}
1072
1073/**
1074 * Main function for parsing and allocating a client request.
1075 *
1076 * See: https://tools.ietf.org/html/rfc2616#section-2.2
1077 *
1078 * @returns VBox status code.
1079 * @param pClient Client to parse request from.
1080 * @param pszReq Request string with headers to parse.
1081 * @param cbReq Size (in bytes) of request string to parse.
1082 * @param ppReq Where to store the allocated client request on success.
1083 * Needs to be free'd via rtHttpServerReqFree().
1084 */
1085static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, const char *pszReq, size_t cbReq,
1086 PRTHTTPSERVERREQ *ppReq)
1087{
1088 RT_NOREF(pClient);
1089
1090 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
1091 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
1092
1093 /* We only support UTF-8 charset for now. */
1094 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
1095
1096 char **ppapszReq = NULL;
1097 size_t cReq = 0;
1098 int rc = RTStrSplit(pszReq, cbReq, RTHTTPSERVER_HTTP11_EOL_STR, &ppapszReq, &cReq);
1099 if (RT_FAILURE(rc))
1100 return rc;
1101
1102 if (!cReq)
1103 return VERR_INVALID_PARAMETER;
1104
1105#ifdef LOG_ENABLED
1106 for (size_t i = 0; i < cReq; i++)
1107 LogFlowFunc(("%s\n", ppapszReq[i]));
1108#endif
1109
1110 PRTHTTPSERVERREQ pReq = NULL;
1111
1112 char **ppapszFirstLine = NULL;
1113 size_t cFirstLine = 0;
1114 rc = RTStrSplit(ppapszReq[0], strlen(ppapszReq[0]), " ", &ppapszFirstLine, &cFirstLine);
1115 if (RT_SUCCESS(rc))
1116 {
1117 if (cFirstLine < 3) /* At leat the method, path and version has to be present. */
1118 rc = VERR_INVALID_PARAMETER;
1119 }
1120
1121 while (RT_SUCCESS(rc)) /* To use break. */
1122 {
1123 pReq = rtHttpServerReqAlloc();
1124 AssertPtrBreakStmt(pReq, rc = VERR_NO_MEMORY);
1125
1126 /*
1127 * Parse method to use. Method names are case sensitive.
1128 */
1129 const char *pszMethod = ppapszFirstLine[0];
1130 if (!RTStrCmp(pszMethod, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
1131 else if (!RTStrCmp(pszMethod, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
1132#ifdef IPRT_HTTP_WITH_WEBDAV
1133 else if (!RTStrCmp(pszMethod, "OPTIONS")) pReq->enmMethod = RTHTTPMETHOD_OPTIONS;
1134 else if (!RTStrCmp(pszMethod, "PROPFIND")) pReq->enmMethod = RTHTTPMETHOD_PROPFIND;
1135#endif
1136 else
1137 {
1138 rc = VERR_NOT_SUPPORTED;
1139 break;
1140 }
1141
1142 /*
1143 * Parse requested path.
1144 */
1145 /** @todo Do URL unescaping here. */
1146 const char *pszPath = ppapszFirstLine[1];
1147 if (!rtHttpServerPathIsValid(pszPath, false /* fIsAbsolute */))
1148 {
1149 rc = VERR_PATH_NOT_FOUND;
1150 break;
1151 }
1152
1153 pReq->pszUrl = RTStrDup(pszPath);
1154
1155 /*
1156 * Parse HTTP version to use.
1157 * We're picky here: Only HTTP 1.1 is supported by now.
1158 */
1159 const char *pszVer = ppapszFirstLine[2];
1160 if (RTStrCmp(pszVer, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
1161 {
1162 rc = VERR_NOT_SUPPORTED;
1163 break;
1164 }
1165
1166 /** @todo Anything else needed for the first line here? */
1167
1168 /*
1169 * Process headers, if any.
1170 */
1171 if (cReq > 1)
1172 {
1173 rc = rtHttpServerParseHeaders(pReq->hHdrLst, cReq - 1, &ppapszReq[1]);
1174 if (RT_SUCCESS(rc))
1175 {
1176 if (RTHttpHeaderListGet(pReq->hHdrLst, "Connection", RTSTR_MAX))
1177 pClient->State.msKeepAlive = RT_MS_30SEC; /** @todo Insert the real value here. */
1178 }
1179 }
1180 break;
1181 } /* for (;;) */
1182
1183 /*
1184 * Cleanup.
1185 */
1186
1187 for (size_t i = 0; i < cFirstLine; i++)
1188 RTStrFree(ppapszFirstLine[i]);
1189 RTMemFree(ppapszFirstLine);
1190
1191 for (size_t i = 0; i < cReq; i++)
1192 RTStrFree(ppapszReq[i]);
1193 RTMemFree(ppapszReq);
1194
1195 if (RT_FAILURE(rc))
1196 {
1197 rtHttpServerReqFree(pReq);
1198 pReq = NULL;
1199 }
1200 else
1201 *ppReq = pReq;
1202
1203 return rc;
1204}
1205
1206/**
1207 * Main function for processing client requests.
1208 *
1209 * @returns VBox status code.
1210 * @param pClient Client to process request for.
1211 * @param pszReq Request string to parse and handle.
1212 * @param cbReq Size (in bytes) of request string.
1213 */
1214static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
1215{
1216 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
1217
1218 PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
1219 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
1220 if (RT_SUCCESS(rc))
1221 {
1222 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
1223
1224 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRequestBegin, pReq);
1225
1226 unsigned i = 0;
1227 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
1228 {
1229 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
1230 if (pReq->enmMethod == pMethodEntry->enmMethod)
1231 {
1232 /* Hand in request to method handler. */
1233 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
1234 if (RT_FAILURE(rcMethod))
1235 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
1236 break;
1237 }
1238 }
1239
1240 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRequestEnd, pReq);
1241
1242 if (i == RT_ELEMENTS(g_aMethodMap))
1243 enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
1244
1245 rtHttpServerReqFree(pReq);
1246 }
1247 else
1248 enmSts = RTHTTPSTATUS_BADREQUEST;
1249
1250 /* If a status was set here explicitly, return it to prevent client hangs. */
1251 if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET)
1252 {
1253 int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
1254 if (RT_SUCCESS(rc))
1255 rc = rc2;
1256 }
1257
1258 LogFlowFuncLeaveRC(rc);
1259 return rc;
1260}
1261
1262/**
1263 * Main loop for processing client requests.
1264 *
1265 * @returns VBox status code.
1266 * @param pClient Client to process requests for.
1267 * @param msTimeout Timeout to wait for reading data.
1268 * Gets renewed for a each reading round.
1269 */
1270static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient, RTMSINTERVAL msTimeout)
1271{
1272 int rc;
1273
1274 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
1275
1276 LogFlowFunc(("Client connected\n"));
1277
1278 /* Initialize client state. */
1279 pClient->State.msKeepAlive = 0;
1280
1281 RTMSINTERVAL cWaitMs = msTimeout;
1282 uint64_t tsLastReadMs = 0;
1283
1284 for (;;) /* For keep-alive handling. */
1285 {
1286 rc = RTTcpSelectOne(pClient->hSocket, cWaitMs);
1287 if (RT_FAILURE(rc))
1288 {
1289 Log2Func(("RTTcpSelectOne=%Rrc (cWaitMs=%RU64)\n", rc, cWaitMs));
1290 if (rc == VERR_TIMEOUT)
1291 {
1292 if (pClient->State.msKeepAlive) /* Keep alive handling needed? */
1293 {
1294 if (!tsLastReadMs)
1295 tsLastReadMs = RTTimeMilliTS();
1296 const uint64_t tsDeltaMs = pClient->State.msKeepAlive - (RTTimeMilliTS() - tsLastReadMs);
1297 Log2Func(("tsLastReadMs=%RU64, tsDeltaMs=%RU64\n", tsLastReadMs, tsDeltaMs));
1298 Log2Func(("Keep alive active (%RU32ms): %RU64ms remaining\n", pClient->State.msKeepAlive, tsDeltaMs));
1299 if ( tsDeltaMs > cWaitMs
1300 && tsDeltaMs < pClient->State.msKeepAlive)
1301 continue;
1302
1303 LogFunc(("Keep alive active: Client did not respond within %RU32ms, closing\n", pClient->State.msKeepAlive));
1304 rc = VINF_SUCCESS;
1305 break;
1306 }
1307 }
1308
1309 break;
1310 }
1311
1312 LogFlowFunc(("Reading client request ...\n"));
1313
1314 tsLastReadMs = RTTimeMilliTS();
1315 cWaitMs = 200; /* All consequtive waits do busy waiting for now. */
1316
1317 char *pszReq = szReq;
1318 size_t cbRead;
1319 size_t cbToRead = sizeof(szReq);
1320 size_t cbReadTotal = 0;
1321
1322 do
1323 {
1324 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
1325 if (RT_FAILURE(rc))
1326 break;
1327
1328 if (!cbRead)
1329 break;
1330
1331 /* Make sure to terminate string read so far. */
1332 pszReq[cbRead] = '\0';
1333
1334 /* End of request reached? */
1335 /** @todo BUGBUG Improve this. */
1336 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
1337 if (pszEOR)
1338 {
1339 cbReadTotal = pszEOR - pszReq;
1340 *pszEOR = '\0';
1341 break;
1342 }
1343
1344 AssertBreak(cbToRead >= cbRead);
1345 cbToRead -= cbRead;
1346 cbReadTotal += cbRead;
1347 AssertBreak(cbReadTotal <= sizeof(szReq));
1348 pszReq += cbRead;
1349
1350 } while (cbToRead);
1351
1352 Log2Func(("Read client request done (%zu bytes) -> rc=%Rrc\n", cbReadTotal, rc));
1353
1354 if ( RT_SUCCESS(rc)
1355 && cbReadTotal)
1356 {
1357 rtHttpServerLogProto(pClient, false /* fWrite */, szReq);
1358
1359 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
1360 }
1361
1362 break;
1363
1364 } /* for */
1365
1366 if (RT_FAILURE(rc))
1367 {
1368 switch (rc)
1369 {
1370 case VERR_NET_CONNECTION_RESET_BY_PEER:
1371 {
1372 LogFunc(("Client closed the connection\n"));
1373 rc = VINF_SUCCESS;
1374 break;
1375 }
1376
1377 default:
1378 LogFunc(("Client processing failed with %Rrc\n", rc));
1379 break;
1380 }
1381 }
1382
1383 LogFlowFuncLeaveRC(rc);
1384 return rc;
1385}
1386
1387/**
1388 * Per-client thread for serving the server's control connection.
1389 *
1390 * @returns VBox status code.
1391 * @param hSocket Socket handle to use for the control connection.
1392 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
1393 */
1394static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
1395{
1396 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
1397 RTHTTPSERVER_VALID_RETURN(pThis);
1398
1399 LogFlowFuncEnter();
1400
1401 RTHTTPSERVERCLIENT Client;
1402 RT_ZERO(Client);
1403
1404 Client.pServer = pThis;
1405 Client.hSocket = hSocket;
1406
1407 return rtHttpServerClientMain(&Client, RT_MS_30SEC /* Timeout */);
1408}
1409
1410RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
1411 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1412{
1413 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
1414 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
1415 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1416 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1417 /* pvUser is optional. */
1418
1419 int rc;
1420
1421 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
1422 if (pThis)
1423 {
1424 pThis->u32Magic = RTHTTPSERVER_MAGIC;
1425 pThis->Callbacks = *pCallbacks;
1426 pThis->pvUser = pvUser;
1427 pThis->cbUser = cbUser;
1428
1429 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
1430 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1431 if (RT_SUCCESS(rc))
1432 {
1433 *hHttpServer = (RTHTTPSERVER)pThis;
1434 }
1435 }
1436 else
1437 rc = VERR_NO_MEMORY;
1438
1439 return rc;
1440}
1441
1442RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
1443{
1444 if (hHttpServer == NIL_RTHTTPSERVER)
1445 return VINF_SUCCESS;
1446
1447 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
1448 RTHTTPSERVER_VALID_RETURN(pThis);
1449
1450 AssertPtr(pThis->pTCPServer);
1451
1452 int rc = VINF_SUCCESS;
1453
1454 PRTHTTPSERVERCALLBACKS pCallbacks = &pThis->Callbacks;
1455 if (pCallbacks->pfnDestroy)
1456 {
1457 RTHTTPCALLBACKDATA Data = { NULL /* pClient */, pThis->pvUser, pThis->cbUser };
1458 rc = pCallbacks->pfnDestroy(&Data);
1459 }
1460
1461 if (RT_SUCCESS(rc))
1462 {
1463 rc = RTTcpServerDestroy(pThis->pTCPServer);
1464 if (RT_SUCCESS(rc))
1465 {
1466 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
1467
1468 RTMemFree(pThis);
1469 }
1470 }
1471
1472 return rc;
1473}
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