VirtualBox

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

Last change on this file since 87015 was 87013, checked in by vboxsync, 4 years ago

Shared Clipboard/Transfers: Fixed warning. bugref:9874

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.7 KB
Line 
1/* $Id: http-server.cpp 87013 2020-11-27 19:41:36Z 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
18/*
19 * Copyright (C) 2020 Oracle Corporation
20 *
21 * This file is part of VirtualBox Open Source Edition (OSE), as
22 * available from http://www.virtualbox.org. This file is free software;
23 * you can redistribute it and/or modify it under the terms of the GNU
24 * General Public License (GPL) as published by the Free Software
25 * Foundation, in version 2 as it comes in the "COPYING" file of the
26 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
27 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
28 *
29 * The contents of this file may alternatively be used under the terms
30 * of the Common Development and Distribution License Version 1.0
31 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
32 * VirtualBox OSE distribution, in which case the provisions of the
33 * CDDL are applicable instead of those of the GPL.
34 *
35 * You may elect to license modified versions of this file under the
36 * terms and conditions of either the GPL or the CDDL or both.
37 */
38
39
40/*********************************************************************************************************************************
41* Header Files *
42*********************************************************************************************************************************/
43#define LOG_GROUP RTLOGGROUP_HTTP
44#include <iprt/http.h>
45#include <iprt/http-server.h>
46#include "internal/iprt.h"
47#include "internal/magics.h"
48
49#include <iprt/asm.h>
50#include <iprt/assert.h>
51#include <iprt/circbuf.h>
52#include <iprt/ctype.h>
53#include <iprt/err.h>
54#include <iprt/file.h> /* For file mode flags. */
55#include <iprt/getopt.h>
56#include <iprt/mem.h>
57#include <iprt/log.h>
58#include <iprt/path.h>
59#include <iprt/poll.h>
60#include <iprt/socket.h>
61#include <iprt/sort.h>
62#include <iprt/string.h>
63#include <iprt/system.h>
64#include <iprt/tcp.h>
65
66
67/*********************************************************************************************************************************
68* Structures and Typedefs *
69*********************************************************************************************************************************/
70/**
71 * Internal HTTP server instance.
72 */
73typedef struct RTHTTPSERVERINTERNAL
74{
75 /** Magic value. */
76 uint32_t u32Magic;
77 /** Callback table. */
78 RTHTTPSERVERCALLBACKS Callbacks;
79 /** Pointer to TCP server instance. */
80 PRTTCPSERVER pTCPServer;
81 /** Pointer to user-specific data. Optional. */
82 void *pvUser;
83 /** Size of user-specific data. Optional. */
84 size_t cbUser;
85} RTHTTPSERVERINTERNAL;
86/** Pointer to an internal HTTP server instance. */
87typedef RTHTTPSERVERINTERNAL *PRTHTTPSERVERINTERNAL;
88
89
90/*********************************************************************************************************************************
91* Defined Constants And Macros *
92*********************************************************************************************************************************/
93/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
94#define RTHTTPSERVER_VALID_RETURN_RC(hHttpServer, a_rc) \
95 do { \
96 AssertPtrReturn((hHttpServer), (a_rc)); \
97 AssertReturn((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC, (a_rc)); \
98 } while (0)
99
100/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
101#define RTHTTPSERVER_VALID_RETURN(hHttpServer) RTHTTPSERVER_VALID_RETURN_RC((hHttpServer), VERR_INVALID_HANDLE)
102
103/** Validates a handle and returns (void) if not valid. */
104#define RTHTTPSERVER_VALID_RETURN_VOID(hHttpServer) \
105 do { \
106 AssertPtrReturnVoid(hHttpServer); \
107 AssertReturnVoid((hHttpServer)->u32Magic == RTHTTPSERVER_MAGIC); \
108 } while (0)
109
110
111/** Handles a HTTP server callback with no arguments and returns. */
112#define RTHTTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
113 do \
114 { \
115 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
116 if (pCallbacks->a_Name) \
117 { \
118 RTHTTPCALLBACKDATA Data = { &pClient->State }; \
119 return pCallbacks->a_Name(&Data); \
120 } \
121 return VERR_NOT_IMPLEMENTED; \
122 } while (0)
123
124/** Handles a HTTP server callback with no arguments and sets rc accordingly. */
125#define RTHTTPSERVER_HANDLE_CALLBACK(a_Name) \
126 do \
127 { \
128 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
129 if (pCallbacks->a_Name) \
130 { \
131 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
132 rc = pCallbacks->a_Name(&Data); \
133 } \
134 else \
135 rc = VERR_NOT_IMPLEMENTED; \
136 } while (0)
137
138/** Handles a HTTP server callback with arguments and sets rc accordingly. */
139#define RTHTTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
140 do \
141 { \
142 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
143 if (pCallbacks->a_Name) \
144 { \
145 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
146 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
147 } \
148 else \
149 rc = VERR_NOT_IMPLEMENTED; \
150 } while (0)
151
152/** Handles a HTTP server callback with arguments and returns. */
153#define RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
154 do \
155 { \
156 PRTHTTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
157 if (pCallbacks->a_Name) \
158 { \
159 RTHTTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
160 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
161 } \
162 return VERR_NOT_IMPLEMENTED; \
163 } while (0)
164
165
166/*********************************************************************************************************************************
167* Structures and Typedefs *
168*********************************************************************************************************************************/
169
170/**
171 * Structure for maintaining an internal HTTP server client.
172 */
173typedef struct RTHTTPSERVERCLIENT
174{
175 /** Pointer to internal server state. */
176 PRTHTTPSERVERINTERNAL pServer;
177 /** Socket handle the client is bound to. */
178 RTSOCKET hSocket;
179 /** Actual client state. */
180 RTHTTPSERVERCLIENTSTATE State;
181} RTHTTPSERVERCLIENT;
182/** Pointer to an internal HTTP server client state. */
183typedef RTHTTPSERVERCLIENT *PRTHTTPSERVERCLIENT;
184
185/** Function pointer declaration for a specific HTTP server method handler. */
186typedef DECLCALLBACKTYPE(int, FNRTHTTPSERVERMETHOD,(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq));
187/** Pointer to a FNRTHTTPSERVERMETHOD(). */
188typedef FNRTHTTPSERVERMETHOD *PFNRTHTTPSERVERMETHOD;
189
190/**
191 * Static lookup table for some file extensions <-> MIME type. Add more as needed.
192 * Keep this alphabetical (file extension).
193 */
194static const struct
195{
196 /** File extension. */
197 const char *pszExt;
198 /** MIME type. */
199 const char *pszMIMEType;
200} s_aFileExtMIMEType[] = {
201 { ".arj", "application/x-arj-compressed" },
202 { ".asf", "video/x-ms-asf" },
203 { ".avi", "video/x-msvideo" },
204 { ".bmp", "image/bmp" },
205 { ".css", "text/css" },
206 { ".doc", "application/msword" },
207 { ".exe", "application/octet-stream" },
208 { ".gif", "image/gif" },
209 { ".gz", "application/x-gunzip" },
210 { ".htm", "text/html" },
211 { ".html", "text/html" },
212 { ".ico", "image/x-icon" },
213 { ".js", "application/x-javascript" },
214 { ".json", "text/json" },
215 { ".jpg", "image/jpeg" },
216 { ".jpeg", "image/jpeg" },
217 { ".ogg", "application/ogg" },
218 { ".m3u", "audio/x-mpegurl" },
219 { ".m4v", "video/x-m4v" },
220 { ".mid", "audio/mid" },
221 { ".mov", "video/quicktime" },
222 { ".mp3", "audio/x-mp3" },
223 { ".mp4", "video/mp4" },
224 { ".mpg", "video/mpeg" },
225 { ".mpeg", "video/mpeg" },
226 { ".pdf", "application/pdf" },
227 { ".png", "image/png" },
228 { ".ra", "audio/x-pn-realaudio" },
229 { ".ram", "audio/x-pn-realaudio" },
230 { ".rar", "application/x-arj-compressed" },
231 { ".rtf", "application/rtf" },
232 { ".shtm", "text/html" },
233 { ".shtml", "text/html" },
234 { ".svg", "image/svg+xml" },
235 { ".swf", "application/x-shockwave-flash" },
236 { ".torrent", "application/x-bittorrent" },
237 { ".tar", "application/x-tar" },
238 { ".tgz", "application/x-tar-gz" },
239 { ".ttf", "application/x-font-ttf" },
240 { ".txt", "text/plain" },
241 { ".wav", "audio/x-wav" },
242 { ".webm", "video/webm" },
243 { ".xml", "text/xml" },
244 { ".xls", "application/excel" },
245 { ".xsl", "application/xml" },
246 { ".xslt", "application/xml" },
247 { ".zip", "application/x-zip-compressed" },
248 { NULL, NULL }
249};
250
251
252/*********************************************************************************************************************************
253* Internal Functions *
254*********************************************************************************************************************************/
255
256/** @name Method handlers.
257 * @{
258 */
259static FNRTHTTPSERVERMETHOD rtHttpServerHandleGET;
260static FNRTHTTPSERVERMETHOD rtHttpServerHandleHEAD;
261/** @} */
262
263/**
264 * Structure for maintaining a single method entry for the methods table.
265 */
266typedef struct RTHTTPSERVERMETHOD_ENTRY
267{
268 /** Method ID. */
269 RTHTTPMETHOD enmMethod;
270 /** Function pointer invoked to handle the command. */
271 PFNRTHTTPSERVERMETHOD pfnMethod;
272} RTHTTPSERVERMETHOD_ENTRY;
273/** Pointer to a command entry. */
274typedef RTHTTPSERVERMETHOD_ENTRY *PRTHTTPMETHOD_ENTRY;
275
276
277
278/*********************************************************************************************************************************
279* Global Variables *
280*********************************************************************************************************************************/
281/**
282 * Table of handled methods.
283 */
284static const RTHTTPSERVERMETHOD_ENTRY g_aMethodMap[] =
285{
286 { RTHTTPMETHOD_GET, rtHttpServerHandleGET },
287 { RTHTTPMETHOD_HEAD, rtHttpServerHandleHEAD },
288 { RTHTTPMETHOD_END, NULL }
289};
290
291/** Maximum length in characters a HTTP server path can have (excluding termination). */
292#define RTHTTPSERVER_MAX_PATH RTPATH_MAX
293
294
295/*********************************************************************************************************************************
296* Internal functions *
297*********************************************************************************************************************************/
298
299/**
300 * Guesses the HTTP MIME type based on a given file extension.
301 *
302 * Note: Has to include the beginning dot, e.g. ".mp3" (see IPRT).
303 *
304 * @returns Guessed MIME type, or "application/octet-stream" if not found.
305 * @param pszFileExt File extension to guess MIME type for.
306 */
307static const char *rtHttpServerGuessMIMEType(const char *pszFileExt)
308{
309 if (pszFileExt)
310 {
311 size_t i = 0;
312 while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */
313 {
314 if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt))
315 return s_aFileExtMIMEType[i].pszMIMEType;
316 }
317 }
318
319 return "application/octet-stream";
320}
321
322/**
323 * Allocates and initializes a new client request.
324 *
325 * @returns Pointer to the new client request, or NULL on OOM.
326 * Needs to be free'd with rtHttpServerReqFree().
327 */
328static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void)
329{
330 PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ));
331 AssertPtrReturn(pReq, NULL);
332
333 int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst);
334 AssertRC(rc2);
335
336 return pReq;
337}
338
339/**
340 * Frees a formerly allocated client request.
341 *
342 * @param pReq Pointer to client request to free.
343 */
344static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq)
345{
346 if (!pReq)
347 return;
348
349 RTHttpHeaderListDestroy(pReq->hHdrLst);
350
351 RTMemFree(pReq->pvBody);
352 pReq->pvBody = NULL;
353
354 RTMemFree(pReq);
355}
356
357
358/*********************************************************************************************************************************
359* Protocol Functions *
360*********************************************************************************************************************************/
361
362/**
363 * Main function for sending a response back to the client.
364 *
365 * @returns VBox status code.
366 * @param pClient Client to reply to.
367 * @param enmSts Status code to send.
368 * @param pHdrLst Header list to send. Optional and can be NULL.
369 */
370static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient,
371 RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
372{
373 char *pszHdr;
374 int rc = RTStrAPrintf(&pszHdr,
375 "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
376 AssertRCReturn(rc, rc);
377
378 RTHTTPHEADERLIST HdrLst;
379 rc = RTHttpHeaderListInit(&HdrLst);
380 AssertRCReturn(rc, rc);
381
382#ifdef DEBUG
383 /* Include a timestamp when running a debug build. */
384 RTTIMESPEC tsNow;
385 char szTS[64];
386 RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
387 rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
388 AssertRCReturn(rc, rc);
389#endif
390
391 /* Note: Deliberately don't include the VBox version due to security reasons. */
392 rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
393 AssertRCReturn(rc, rc);
394
395 size_t i = 0;
396 const char *pszEntry;
397 while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
398 {
399 rc = RTStrAAppend(&pszHdr, pszEntry);
400 AssertRCBreak(rc);
401 rc = RTStrAAppend(&pszHdr, "\r\n");
402 AssertRCBreak(rc);
403 }
404
405 /* Append optional headers, if any. */
406 if (pHdrLst)
407 {
408 i = 0;
409 while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
410 {
411 rc = RTStrAAppend(&pszHdr, pszEntry);
412 AssertRCBreak(rc);
413 rc = RTStrAAppend(&pszHdr, "\r\n");
414 AssertRCBreak(rc);
415 }
416 }
417
418 if (RT_SUCCESS(rc))
419 {
420 /* Append trailing EOL. */
421 rc = RTStrAAppend(&pszHdr, "\r\n");
422 if (RT_SUCCESS(rc))
423 rc = RTTcpWrite(pClient->hSocket, pszHdr, strlen(pszHdr));
424 }
425
426 RTStrFree(pszHdr);
427
428 RTHttpHeaderListDestroy(HdrLst);
429
430 LogFlowFunc(("enmStatus=%s, rc=%Rrc\n", RTHttpStatusToStr(enmSts), rc));
431 return rc;
432}
433
434/**
435 * Replies with (three digit) response status back to the client, extended version.
436 *
437 * @returns VBox status code.
438 * @param pClient Client to reply to.
439 * @param enmSts Status code to send.
440 * @param pHdrLst Header list to send. Optional and can be NULL.
441 */
442static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
443{
444 int rc = rtHttpServerSendResponseHdrEx(pClient, enmSts, pHdrLst);
445
446 return rc;
447}
448
449/**
450 * Replies with (three digit) response status back to the client.
451 *
452 * @returns VBox status code.
453 * @param pClient Client to reply to.
454 * @param enmSts Status code to send.
455 */
456static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
457{
458 return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
459}
460
461/**
462 * Sends a chunk of the response body to the client.
463 *
464 * @returns VBox status code.
465 * @param pClient Client to send body to.
466 * @param pvBuf Data buffer to send.
467 * @param cbBuf Size (in bytes) of data buffer to send.
468 * @param pcbSent Where to store the sent bytes. Optional and can be NULL.
469 */
470static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
471{
472 int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
473 if ( RT_SUCCESS(rc)
474 && pcbSent)
475 *pcbSent = cbBuf;
476
477 return rc;
478}
479
480/**
481 * Resolves a VBox status code to a HTTP status code.
482 *
483 * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve.
484 * @param rc VBox status code to resolve.
485 */
486static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
487{
488 switch (rc)
489 {
490 case VINF_SUCCESS: return RTHTTPSTATUS_OK;
491 case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
492 case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
493 case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
494 case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
495 case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
496 case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
497 case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
498 default:
499 break;
500 }
501
502 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
503 return RTHTTPSTATUS_INTERNALSERVERERROR;
504}
505
506
507/*********************************************************************************************************************************
508* Command Protocol Handlers *
509*********************************************************************************************************************************/
510
511/**
512 * Handler for the GET method.
513 *
514 * @returns VBox status code.
515 * @param pClient Client to handle GET method for.
516 * @param pReq Client request to handle.
517 */
518static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
519{
520 LogFlowFuncEnter();
521
522 int rc;
523
524 RTFSOBJINFO fsObj;
525 RT_ZERO(fsObj); /* Shut up MSVC. */
526 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj);
527 if (RT_FAILURE(rc))
528 return rc;
529
530 uint64_t uID = 0; /* Ditto. */
531 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq->pszUrl, &uID);
532
533 if (RT_SUCCESS(rc))
534 {
535 size_t cbBuf = _64K;
536 void *pvBuf = RTMemAlloc(cbBuf);
537 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
538
539 for (;;)
540 {
541 RTHTTPHEADERLIST HdrLst;
542 rc = RTHttpHeaderListInit(&HdrLst);
543 AssertRCReturn(rc, rc);
544
545 char szVal[16];
546
547 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
548 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
549 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
550 AssertRCBreak(rc);
551
552 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
553 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
554 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
555 AssertRCBreak(rc);
556
557 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
558 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
559 AssertRCReturn(rc, rc);
560
561 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
562 AssertRCReturn(rc, rc);
563
564 RTHttpHeaderListDestroy(HdrLst);
565
566 size_t cbToRead = fsObj.cbObject;
567 size_t cbRead = 0; /* Shut up GCC. */
568 size_t cbWritten = 0; /* Ditto. */
569 while (cbToRead)
570 {
571 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, uID, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
572 if (RT_FAILURE(rc))
573 break;
574 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
575 AssertBreak(cbToRead >= cbWritten);
576 cbToRead -= cbWritten;
577 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
578 {
579 rc = VINF_SUCCESS;
580 break;
581 }
582 AssertRCBreak(rc);
583 }
584
585 break;
586 } /* for (;;) */
587
588 RTMemFree(pvBuf);
589
590 int rc2 = rc; /* Save rc. */
591
592 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, uID);
593
594 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
595 rc = rc2;
596 }
597
598 LogFlowFuncLeaveRC(rc);
599 return rc;
600}
601
602/**
603 * Handler for the HEAD method.
604 *
605 * @returns VBox status code.
606 * @param pClient Client to handle HEAD method for.
607 * @param pReq Client request to handle.
608 */
609static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
610{
611 LogFlowFuncEnter();
612
613 int rc;
614
615 RTFSOBJINFO fsObj;
616 RT_ZERO(fsObj); /* Shut up MSVC. */
617 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj);
618 if (RT_SUCCESS(rc))
619 {
620 RTHTTPHEADERLIST HdrLst;
621 rc = RTHttpHeaderListInit(&HdrLst);
622 AssertRCReturn(rc, rc);
623
624 /*
625 * Note: A response to a HEAD request does not have a body.
626 * All entity headers below are assumed to describe the the response a similar GET
627 * request would return (but then with a body).
628 */
629 char szVal[16];
630
631 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
632 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
633 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
634 AssertRCReturn(rc, rc);
635
636 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
637 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
638 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
639 AssertRCReturn(rc, rc);
640
641 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
642 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
643 AssertRCReturn(rc, rc);
644
645 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
646 AssertRCReturn(rc, rc);
647
648 RTHttpHeaderListDestroy(HdrLst);
649 }
650
651 LogFlowFuncLeaveRC(rc);
652 return rc;
653}
654
655/**
656 * Validates if a given path is valid or not.
657 *
658 * @returns \c true if path is valid, or \c false if not.
659 * @param pszPath Path to check.
660 * @param fIsAbsolute Whether the path to check is an absolute path or not.
661 */
662static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
663{
664 if (!pszPath)
665 return false;
666
667 bool fIsValid = strlen(pszPath)
668 && RTStrIsValidEncoding(pszPath)
669 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
670 if ( fIsValid
671 && fIsAbsolute)
672 {
673 RTFSOBJINFO objInfo;
674 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
675 if (RT_SUCCESS(rc2))
676 {
677 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
678 || RTFS_IS_FILE(objInfo.Attr.fMode);
679
680 /* No symlinks and other stuff not allowed. */
681 }
682 else
683 fIsValid = false;
684 }
685
686 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
687 return fIsValid;
688
689}
690
691/**
692 * Parses headers and fills them into a given header list.
693 *
694 * @returns VBox status code.
695 * @param hList Header list to fill parsed headers in.
696 * @param pszReq Request string with headers to parse.
697 * @param cbReq Size (in bytes) of request string to parse.
698 */
699static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, char *pszReq, size_t cbReq)
700{
701 /* Nothing to parse left? Bail out early. */
702 if ( !pszReq
703 || !cbReq)
704 return VINF_SUCCESS;
705
706 /* Nothing to do here yet. */
707 RT_NOREF(hList);
708
709 return VINF_SUCCESS;
710}
711
712/**
713 * Main function for parsing and allocating a client request.
714 *
715 * @returns VBox status code.
716 * @param pClient Client to parse request from.
717 * @param pszReq Request string with headers to parse.
718 * @param cbReq Size (in bytes) of request string to parse.
719 * @param ppReq Where to store the allocated client request on success.
720 * Needs to be free'd via rtHttpServerReqFree().
721 */
722static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq,
723 PRTHTTPSERVERREQ *ppReq)
724{
725 RT_NOREF(pClient);
726
727 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
728 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
729
730 /* We only support UTF-8 charset for now. */
731 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
732
733 /** Advances pszReq to the next string in the request.
734 ** @todo Can we do better here? */
735#define REQ_GET_NEXT_STRING \
736 pszReq = psz; \
737 while (psz && !RT_C_IS_SPACE(*psz)) \
738 psz++; \
739 if (psz) \
740 { \
741 *psz = '\0'; \
742 psz++; \
743 } \
744 else /* Be strict for now. */ \
745 AssertFailedReturn(VERR_INVALID_PARAMETER);
746
747 char *psz = pszReq;
748
749 /* Make sure to terminate the string in any case. */
750 psz[RT_MIN(RTHTTPSERVER_MAX_REQ_LEN - 1, cbReq)] = '\0';
751
752 /* A tiny bit of sanitation. */
753 RTStrStrip(psz);
754
755 PRTHTTPSERVERREQ pReq = rtHttpServerReqAlloc();
756 AssertPtrReturn(pReq, VERR_NO_MEMORY);
757
758 REQ_GET_NEXT_STRING
759
760 /* Note: Method names are case sensitive. */
761 if (!RTStrCmp(pszReq, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
762 else if (!RTStrCmp(pszReq, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
763 else
764 return VERR_NOT_SUPPORTED;
765
766 REQ_GET_NEXT_STRING
767
768 pReq->pszUrl = RTStrDup(pszReq);
769
770 if (!rtHttpServerPathIsValid(pReq->pszUrl, false /* fIsAbsolute */))
771 return VERR_PATH_NOT_FOUND;
772
773 REQ_GET_NEXT_STRING
774
775 /* Only HTTP 1.1 is supported by now. */
776 if (RTStrCmp(pszReq, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
777 return VERR_NOT_SUPPORTED;
778
779 int rc = rtHttpServerParseHeaders(pReq->hHdrLst, pszReq, cbReq);
780 if (RT_SUCCESS(rc))
781 *ppReq = pReq;
782
783 return rc;
784}
785
786/**
787 * Main function for processing client requests.
788 *
789 * @returns VBox status code.
790 * @param pClient Client to process request for.
791 * @param pszReq Request string to parse and handle.
792 * @param cbReq Size (in bytes) of request string.
793 */
794static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
795{
796 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
797
798 PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
799 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
800 if (RT_SUCCESS(rc))
801 {
802 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
803
804 unsigned i = 0;
805 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
806 {
807 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
808 if (pReq->enmMethod == pMethodEntry->enmMethod)
809 {
810 /* Hand in request to method handler. */
811 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
812 if (RT_FAILURE(rcMethod))
813 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
814
815 enmSts = rtHttpServerRcToStatus(rcMethod);
816 break;
817 }
818 }
819
820 if (i == RT_ELEMENTS(g_aMethodMap))
821 enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
822
823 rtHttpServerReqFree(pReq);
824 }
825 else
826 enmSts = RTHTTPSTATUS_BADREQUEST;
827
828 if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET)
829 {
830 int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
831 if (RT_SUCCESS(rc))
832 rc = rc2;
833 }
834
835 LogFlowFuncLeaveRC(rc);
836 return rc;
837}
838
839/**
840 * Main loop for processing client requests.
841 *
842 * @returns VBox status code.
843 * @param pClient Client to process requests for.
844 */
845static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient)
846{
847 int rc;
848
849 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
850
851 LogFlowFunc(("Client connected\n"));
852
853 rc = RTTcpSelectOne(pClient->hSocket, RT_INDEFINITE_WAIT);
854 if (RT_SUCCESS(rc))
855 {
856 char *pszReq = szReq;
857 size_t cbRead;
858 size_t cbToRead = sizeof(szReq);
859 size_t cbReadTotal = 0;
860
861 do
862 {
863 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
864 if (RT_FAILURE(rc))
865 break;
866
867 if (!cbRead)
868 break;
869
870 /* Make sure to terminate string read so far. */
871 pszReq[cbRead] = '\0';
872
873 /* End of request reached? */
874 /** @todo BUGBUG Improve this. */
875 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
876 if (pszEOR)
877 {
878 cbReadTotal = pszEOR - pszReq;
879 *pszEOR = '\0';
880 break;
881 }
882
883 AssertBreak(cbToRead >= cbRead);
884 cbToRead -= cbRead;
885 cbReadTotal += cbRead;
886 AssertBreak(cbReadTotal <= sizeof(szReq));
887 pszReq += cbRead;
888
889 } while (cbToRead);
890
891 if ( RT_SUCCESS(rc)
892 && cbReadTotal)
893 {
894 LogFlowFunc(("Received request (%zu bytes):\n%s\n\n", cbReadTotal, pszReq));
895
896 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
897 }
898 }
899
900 LogFlowFuncLeaveRC(rc);
901 return rc;
902}
903
904/**
905 * Per-client thread for serving the server's control connection.
906 *
907 * @returns VBox status code.
908 * @param hSocket Socket handle to use for the control connection.
909 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
910 */
911static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
912{
913 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
914 RTHTTPSERVER_VALID_RETURN(pThis);
915
916 LogFlowFuncEnter();
917
918 RTHTTPSERVERCLIENT Client;
919 RT_ZERO(Client);
920
921 Client.pServer = pThis;
922 Client.hSocket = hSocket;
923
924 return rtHttpServerClientMain(&Client);
925}
926
927RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
928 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
929{
930 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
931 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
932 AssertReturn (uPort, VERR_INVALID_PARAMETER);
933 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
934 /* pvUser is optional. */
935
936 int rc;
937
938 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
939 if (pThis)
940 {
941 pThis->u32Magic = RTHTTPSERVER_MAGIC;
942 pThis->Callbacks = *pCallbacks;
943 pThis->pvUser = pvUser;
944 pThis->cbUser = cbUser;
945
946 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
947 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
948 if (RT_SUCCESS(rc))
949 {
950 *hHttpServer = (RTHTTPSERVER)pThis;
951 }
952 }
953 else
954 rc = VERR_NO_MEMORY;
955
956 return rc;
957}
958
959RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
960{
961 if (hHttpServer == NIL_RTHTTPSERVER)
962 return VINF_SUCCESS;
963
964 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
965 RTHTTPSERVER_VALID_RETURN(pThis);
966
967 AssertPtr(pThis->pTCPServer);
968
969 int rc = RTTcpServerDestroy(pThis->pTCPServer);
970 if (RT_SUCCESS(rc))
971 {
972 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
973
974 RTMemFree(pThis);
975 }
976
977 return rc;
978}
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