VirtualBox

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

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

Shared Clipboard/Transfers: A bit of cleanup. bugref:9874

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.8 KB
Line 
1/* $Id: http-server.cpp 87029 2020-12-02 08:28:43Z 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 } while (0)
163
164
165/*********************************************************************************************************************************
166* Structures and Typedefs *
167*********************************************************************************************************************************/
168
169/**
170 * Structure for maintaining an internal HTTP server client.
171 */
172typedef struct RTHTTPSERVERCLIENT
173{
174 /** Pointer to internal server state. */
175 PRTHTTPSERVERINTERNAL pServer;
176 /** Socket handle the client is bound to. */
177 RTSOCKET hSocket;
178 /** Actual client state. */
179 RTHTTPSERVERCLIENTSTATE State;
180} RTHTTPSERVERCLIENT;
181/** Pointer to an internal HTTP server client state. */
182typedef RTHTTPSERVERCLIENT *PRTHTTPSERVERCLIENT;
183
184/** Function pointer declaration for a specific HTTP server method handler. */
185typedef DECLCALLBACKTYPE(int, FNRTHTTPSERVERMETHOD,(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq));
186/** Pointer to a FNRTHTTPSERVERMETHOD(). */
187typedef FNRTHTTPSERVERMETHOD *PFNRTHTTPSERVERMETHOD;
188
189/**
190 * Static lookup table for some file extensions <-> MIME type. Add more as needed.
191 * Keep this alphabetical (file extension).
192 */
193static const struct
194{
195 /** File extension. */
196 const char *pszExt;
197 /** MIME type. */
198 const char *pszMIMEType;
199} s_aFileExtMIMEType[] = {
200 { ".arj", "application/x-arj-compressed" },
201 { ".asf", "video/x-ms-asf" },
202 { ".avi", "video/x-msvideo" },
203 { ".bmp", "image/bmp" },
204 { ".css", "text/css" },
205 { ".doc", "application/msword" },
206 { ".exe", "application/octet-stream" },
207 { ".gif", "image/gif" },
208 { ".gz", "application/x-gunzip" },
209 { ".htm", "text/html" },
210 { ".html", "text/html" },
211 { ".ico", "image/x-icon" },
212 { ".js", "application/x-javascript" },
213 { ".json", "text/json" },
214 { ".jpg", "image/jpeg" },
215 { ".jpeg", "image/jpeg" },
216 { ".ogg", "application/ogg" },
217 { ".m3u", "audio/x-mpegurl" },
218 { ".m4v", "video/x-m4v" },
219 { ".mid", "audio/mid" },
220 { ".mov", "video/quicktime" },
221 { ".mp3", "audio/x-mp3" },
222 { ".mp4", "video/mp4" },
223 { ".mpg", "video/mpeg" },
224 { ".mpeg", "video/mpeg" },
225 { ".pdf", "application/pdf" },
226 { ".png", "image/png" },
227 { ".ra", "audio/x-pn-realaudio" },
228 { ".ram", "audio/x-pn-realaudio" },
229 { ".rar", "application/x-arj-compressed" },
230 { ".rtf", "application/rtf" },
231 { ".shtm", "text/html" },
232 { ".shtml", "text/html" },
233 { ".svg", "image/svg+xml" },
234 { ".swf", "application/x-shockwave-flash" },
235 { ".torrent", "application/x-bittorrent" },
236 { ".tar", "application/x-tar" },
237 { ".tgz", "application/x-tar-gz" },
238 { ".ttf", "application/x-font-ttf" },
239 { ".txt", "text/plain" },
240 { ".wav", "audio/x-wav" },
241 { ".webm", "video/webm" },
242 { ".xml", "text/xml" },
243 { ".xls", "application/excel" },
244 { ".xsl", "application/xml" },
245 { ".xslt", "application/xml" },
246 { ".zip", "application/x-zip-compressed" },
247 { NULL, NULL }
248};
249
250
251/*********************************************************************************************************************************
252* Internal Functions *
253*********************************************************************************************************************************/
254
255/** @name Method handlers.
256 * @{
257 */
258static FNRTHTTPSERVERMETHOD rtHttpServerHandleGET;
259static FNRTHTTPSERVERMETHOD rtHttpServerHandleHEAD;
260/** @} */
261
262/**
263 * Structure for maintaining a single method entry for the methods table.
264 */
265typedef struct RTHTTPSERVERMETHOD_ENTRY
266{
267 /** Method ID. */
268 RTHTTPMETHOD enmMethod;
269 /** Function pointer invoked to handle the command. */
270 PFNRTHTTPSERVERMETHOD pfnMethod;
271} RTHTTPSERVERMETHOD_ENTRY;
272/** Pointer to a command entry. */
273typedef RTHTTPSERVERMETHOD_ENTRY *PRTHTTPMETHOD_ENTRY;
274
275
276
277/*********************************************************************************************************************************
278* Global Variables *
279*********************************************************************************************************************************/
280/**
281 * Table of handled methods.
282 */
283static const RTHTTPSERVERMETHOD_ENTRY g_aMethodMap[] =
284{
285 { RTHTTPMETHOD_GET, rtHttpServerHandleGET },
286 { RTHTTPMETHOD_HEAD, rtHttpServerHandleHEAD },
287 { RTHTTPMETHOD_END, NULL }
288};
289
290/** Maximum length in characters a HTTP server path can have (excluding termination). */
291#define RTHTTPSERVER_MAX_PATH RTPATH_MAX
292
293
294/*********************************************************************************************************************************
295* Internal functions *
296*********************************************************************************************************************************/
297
298/**
299 * Guesses the HTTP MIME type based on a given file extension.
300 *
301 * Note: Has to include the beginning dot, e.g. ".mp3" (see IPRT).
302 *
303 * @returns Guessed MIME type, or "application/octet-stream" if not found.
304 * @param pszFileExt File extension to guess MIME type for.
305 */
306static const char *rtHttpServerGuessMIMEType(const char *pszFileExt)
307{
308 if (pszFileExt)
309 {
310 size_t i = 0;
311 while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */
312 {
313 if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt))
314 return s_aFileExtMIMEType[i].pszMIMEType;
315 }
316 }
317
318 return "application/octet-stream";
319}
320
321/**
322 * Allocates and initializes a new client request.
323 *
324 * @returns Pointer to the new client request, or NULL on OOM.
325 * Needs to be free'd with rtHttpServerReqFree().
326 */
327static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void)
328{
329 PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ));
330 AssertPtrReturn(pReq, NULL);
331
332 int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst);
333 AssertRC(rc2);
334
335 return pReq;
336}
337
338/**
339 * Frees a formerly allocated client request.
340 *
341 * @param pReq Pointer to client request to free.
342 */
343static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq)
344{
345 if (!pReq)
346 return;
347
348 RTHttpHeaderListDestroy(pReq->hHdrLst);
349
350 RTMemFree(pReq->pvBody);
351 pReq->pvBody = NULL;
352
353 RTMemFree(pReq);
354}
355
356/**
357 * Initializes a HTTP server response with an allocated body size.
358 *
359 * @returns VBox status code.
360 * @param pResp HTTP server response to initialize.
361 * @param cbBody Body size (in bytes) to allocate.
362 */
363RTR3DECL(int) RTHttpServerResponseInitEx(PRTHTTPSERVERRESP pResp, size_t cbBody)
364{
365 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
366
367 int rc = RTHttpHeaderListInit(&pResp->hHdrLst);
368 AssertRCReturn(rc, rc);
369
370 if (cbBody)
371 {
372 pResp->pvBody = RTMemAlloc(cbBody);
373 AssertPtrReturn(pResp->pvBody, VERR_NO_MEMORY);
374 pResp->cbBodyAlloc = cbBody;
375 }
376 else
377 {
378 pResp->cbBodyAlloc = 0;
379 }
380
381 pResp->cbBodyUsed = 0;
382
383 return rc;
384}
385
386/**
387 * Initializes a HTTP server response.
388 *
389 * @returns VBox status code.
390 * @param pResp HTTP server response to initialize.
391 */
392RTR3DECL(int) RTHttpServerResponseInit(PRTHTTPSERVERRESP pResp)
393{
394 return RTHttpServerResponseInitEx(pResp, 0 /* cbBody */);
395}
396
397/**
398 * Destroys a formerly initialized HTTP server response.
399 *
400 * @param pResp Pointer to HTTP server response to destroy.
401 */
402RTR3DECL(void) RTHttpServerResponseDestroy(PRTHTTPSERVERRESP pResp)
403{
404 if (!pResp)
405 return;
406
407 pResp->enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
408
409 RTHttpHeaderListDestroy(pResp->hHdrLst);
410
411 if (pResp->pvBody)
412 {
413 Assert(pResp->cbBodyAlloc);
414
415 RTMemFree(pResp->pvBody);
416 pResp->pvBody = NULL;
417 }
418
419 pResp->cbBodyAlloc = 0;
420 pResp->cbBodyUsed = 0;
421}
422
423
424/*********************************************************************************************************************************
425* Protocol Functions *
426*********************************************************************************************************************************/
427
428/**
429 * Logs the HTTP protocol communication to the debug logger (2).
430 *
431 * @param pClient Client to log communication for.
432 * @param fWrite Whether the server is writing (to client) or reading (from client).
433 * @param pszData Actual protocol communication data to log.
434 */
435static void rtHttpServerLogProto(PRTHTTPSERVERCLIENT pClient, bool fWrite, const char *pszData)
436{
437 RT_NOREF(pClient);
438
439#ifdef LOG_ENABLED
440 if (!pszData) /* Nothing to log? Bail out. */
441 return;
442
443 char **ppapszStrings;
444 size_t cStrings;
445 int rc2 = RTStrSplit(pszData, strlen(pszData), "\r\n", &ppapszStrings, &cStrings);
446 if (RT_SUCCESS(rc2))
447 {
448 for (size_t i = 0; i < cStrings; i++)
449 {
450 Log2(("%s %s\n", fWrite ? ">" : "<", ppapszStrings[i]));
451 RTStrFree(ppapszStrings[i]);
452 }
453
454 RTMemFree(ppapszStrings);
455 }
456#else
457 RT_NOREF(fWrite, pszData);
458#endif
459}
460
461/**
462 * Writes HTTP protocol communication data to a connected client.
463 *
464 * @returns VBox status code.
465 * @param pClient Client to write data to.
466 * @param pszData Data to write. Must be zero-terminated.
467 */
468static int rtHttpServerWriteProto(PRTHTTPSERVERCLIENT pClient, const char *pszData)
469{
470 rtHttpServerLogProto(pClient, true /* fWrite */, pszData);
471 return RTTcpWrite(pClient->hSocket, pszData, strlen(pszData));
472}
473
474/**
475 * Main function for sending a response back to the client.
476 *
477 * @returns VBox status code.
478 * @param pClient Client to reply to.
479 * @param enmSts Status code to send.
480 */
481static int rtHttpServerSendResponse(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
482{
483 char *pszResp;
484 int rc = RTStrAPrintf(&pszResp,
485 "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
486 AssertRCReturn(rc, rc);
487 rc = rtHttpServerWriteProto(pClient, pszResp);
488 RTStrFree(pszResp);
489
490 LogFlowFuncLeaveRC(rc);
491 return rc;
492}
493
494/**
495 * Main function for sending response headers back to the client.
496 *
497 * @returns VBox status code.
498 * @param pClient Client to reply to.
499 * @param pHdrLst Header list to send. Optional and can be NULL.
500 */
501static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient, PRTHTTPHEADERLIST pHdrLst)
502{
503 RTHTTPHEADERLIST HdrLst;
504 int rc = RTHttpHeaderListInit(&HdrLst);
505 AssertRCReturn(rc, rc);
506
507#ifdef DEBUG
508 /* Include a timestamp when running a debug build. */
509 RTTIMESPEC tsNow;
510 char szTS[64];
511 RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
512 rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
513 AssertRCReturn(rc, rc);
514#endif
515
516 /* Note: Deliberately don't include the VBox version due to security reasons. */
517 rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
518 AssertRCReturn(rc, rc);
519
520 char *pszHdr = NULL;
521
522 size_t i = 0;
523 const char *pszEntry;
524 while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
525 {
526 rc = RTStrAAppend(&pszHdr, pszEntry);
527 AssertRCBreak(rc);
528 rc = RTStrAAppend(&pszHdr, "\r\n");
529 AssertRCBreak(rc);
530 }
531
532 /* Append optional headers, if any. */
533 if (pHdrLst)
534 {
535 i = 0;
536 while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
537 {
538 rc = RTStrAAppend(&pszHdr, pszEntry);
539 AssertRCBreak(rc);
540 rc = RTStrAAppend(&pszHdr, "\r\n");
541 AssertRCBreak(rc);
542 }
543 }
544
545 if (RT_SUCCESS(rc))
546 {
547 /* Append trailing EOL. */
548 rc = RTStrAAppend(&pszHdr, "\r\n");
549 if (RT_SUCCESS(rc))
550 rc = rtHttpServerWriteProto(pClient, pszHdr);
551 }
552
553 RTStrFree(pszHdr);
554
555 RTHttpHeaderListDestroy(HdrLst);
556
557 LogFlowFunc(("rc=%Rrc\n", rc));
558 return rc;
559}
560
561/**
562 * Replies with (three digit) response status back to the client, extended version.
563 *
564 * @returns VBox status code.
565 * @param pClient Client to reply to.
566 * @param enmSts Status code to send.
567 * @param pHdrLst Header list to send. Optional and can be NULL.
568 */
569static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
570{
571 int rc = rtHttpServerSendResponse(pClient, enmSts);
572 if (RT_SUCCESS(rc))
573 rc = rtHttpServerSendResponseHdrEx(pClient, pHdrLst);
574
575 return rc;
576}
577
578/**
579 * Replies with (three digit) response status back to the client.
580 *
581 * @returns VBox status code.
582 * @param pClient Client to reply to.
583 * @param enmSts Status code to send.
584 */
585static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
586{
587 return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
588}
589
590/**
591 * Sends a chunk of the response body to the client.
592 *
593 * @returns VBox status code.
594 * @param pClient Client to send body to.
595 * @param pvBuf Data buffer to send.
596 * @param cbBuf Size (in bytes) of data buffer to send.
597 * @param pcbSent Where to store the sent bytes. Optional and can be NULL.
598 */
599static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
600{
601 int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
602 if ( RT_SUCCESS(rc)
603 && pcbSent)
604 *pcbSent = cbBuf;
605
606 return rc;
607}
608
609/**
610 * Resolves a VBox status code to a HTTP status code.
611 *
612 * @returns Resolved HTTP status code, or RTHTTPSTATUS_INTERNALSERVERERROR if not able to resolve.
613 * @param rc VBox status code to resolve.
614 */
615static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
616{
617 switch (rc)
618 {
619 case VINF_SUCCESS: return RTHTTPSTATUS_OK;
620 case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
621 case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
622 case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
623 case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
624 case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
625 case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
626 case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
627 default:
628 break;
629 }
630
631 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
632 return RTHTTPSTATUS_INTERNALSERVERERROR;
633}
634
635
636/*********************************************************************************************************************************
637* Command Protocol Handlers *
638*********************************************************************************************************************************/
639
640/**
641 * Handler for the GET method.
642 *
643 * @returns VBox status code.
644 * @param pClient Client to handle GET method for.
645 * @param pReq Client request to handle.
646 */
647static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
648{
649 LogFlowFuncEnter();
650
651 int rc;
652
653 /* If a low-level GET request handler is defined, call it and return. */
654 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnGetRequest, pReq);
655
656 RTFSOBJINFO fsObj;
657 RT_ZERO(fsObj); /* Shut up MSVC. */
658
659 char *pszMIMEHint = NULL;
660
661 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj, &pszMIMEHint);
662 if (RT_FAILURE(rc))
663 return rc;
664
665 void *pvHandle = NULL;
666 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq->pszUrl, &pvHandle);
667
668 if (RT_SUCCESS(rc))
669 {
670 size_t cbBuf = _64K;
671 void *pvBuf = RTMemAlloc(cbBuf);
672 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
673
674 for (;;)
675 {
676 RTHTTPHEADERLIST HdrLst;
677 rc = RTHttpHeaderListInit(&HdrLst);
678 AssertRCReturn(rc, rc);
679
680 char szVal[16];
681
682 /* Note: For directories fsObj.cbObject contains the actual size (in bytes)
683 * of the body data for the directory listing. */
684
685 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
686 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
687 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
688 AssertRCBreak(rc);
689
690 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
691 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
692 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
693 AssertRCBreak(rc);
694
695 if (pszMIMEHint == NULL)
696 {
697 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
698 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
699 }
700 else
701 {
702 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIMEHint, strlen(pszMIMEHint), RTHTTPHEADERLISTADD_F_BACK);
703 RTStrFree(pszMIMEHint);
704 pszMIMEHint = NULL;
705 }
706 AssertRCReturn(rc, rc);
707
708 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
709 AssertRCReturn(rc, rc);
710
711 RTHttpHeaderListDestroy(HdrLst);
712
713 size_t cbToRead = fsObj.cbObject;
714 size_t cbRead = 0; /* Shut up GCC. */
715 size_t cbWritten = 0; /* Ditto. */
716 while (cbToRead)
717 {
718 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, pvHandle, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
719 if (RT_FAILURE(rc))
720 break;
721 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
722 AssertBreak(cbToRead >= cbWritten);
723 cbToRead -= cbWritten;
724 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
725 {
726 rc = VINF_SUCCESS;
727 break;
728 }
729 AssertRCBreak(rc);
730 }
731
732 break;
733 } /* for (;;) */
734
735 RTMemFree(pvBuf);
736
737 int rc2 = rc; /* Save rc. */
738
739 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, pvHandle);
740
741 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
742 rc = rc2;
743 }
744
745 LogFlowFuncLeaveRC(rc);
746 return rc;
747}
748
749/**
750 * Handler for the HEAD method.
751 *
752 * @returns VBox status code.
753 * @param pClient Client to handle HEAD method for.
754 * @param pReq Client request to handle.
755 */
756static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
757{
758 LogFlowFuncEnter();
759
760 /* If a low-level HEAD request handler is defined, call it and return. */
761 RTHTTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnHeadRequest, pReq);
762
763 int rc;
764
765 RTFSOBJINFO fsObj;
766 RT_ZERO(fsObj); /* Shut up MSVC. */
767
768 char *pszMIMEHint = NULL;
769
770 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj, &pszMIMEHint);
771 if (RT_SUCCESS(rc))
772 {
773 RTHTTPHEADERLIST HdrLst;
774 rc = RTHttpHeaderListInit(&HdrLst);
775 AssertRCReturn(rc, rc);
776
777 /*
778 * Note: A response to a HEAD request does not have a body.
779 * All entity headers below are assumed to describe the the response a similar GET
780 * request would return (but then with a body).
781 */
782 char szVal[16];
783
784 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
785 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
786 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
787 AssertRCReturn(rc, rc);
788
789 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
790 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
791 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
792 AssertRCReturn(rc, rc);
793
794 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
795 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
796 AssertRCReturn(rc, rc);
797
798 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
799 AssertRCReturn(rc, rc);
800
801 RTHttpHeaderListDestroy(HdrLst);
802 }
803
804 LogFlowFuncLeaveRC(rc);
805 return rc;
806}
807
808/**
809 * Validates if a given path is valid or not.
810 *
811 * @returns \c true if path is valid, or \c false if not.
812 * @param pszPath Path to check.
813 * @param fIsAbsolute Whether the path to check is an absolute path or not.
814 */
815static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
816{
817 if (!pszPath)
818 return false;
819
820 bool fIsValid = strlen(pszPath)
821 && RTStrIsValidEncoding(pszPath)
822 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
823 if ( fIsValid
824 && fIsAbsolute)
825 {
826 RTFSOBJINFO objInfo;
827 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
828 if (RT_SUCCESS(rc2))
829 {
830 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
831 || RTFS_IS_FILE(objInfo.Attr.fMode);
832
833 /* No symlinks and other stuff not allowed. */
834 }
835 else
836 fIsValid = false;
837 }
838
839 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
840 return fIsValid;
841
842}
843
844/**
845 * Parses headers and fills them into a given header list.
846 *
847 * @returns VBox status code.
848 * @param hList Header list to fill parsed headers in.
849 * @param pszReq Request string with headers to parse.
850 * @param cbReq Size (in bytes) of request string to parse.
851 */
852static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, char *pszReq, size_t cbReq)
853{
854 /* Nothing to parse left? Bail out early. */
855 if ( !pszReq
856 || !cbReq)
857 return VINF_SUCCESS;
858
859 /* Nothing to do here yet. */
860 RT_NOREF(hList);
861
862 return VINF_SUCCESS;
863}
864
865/**
866 * Main function for parsing and allocating a client request.
867 *
868 * @returns VBox status code.
869 * @param pClient Client to parse request from.
870 * @param pszReq Request string with headers to parse.
871 * @param cbReq Size (in bytes) of request string to parse.
872 * @param ppReq Where to store the allocated client request on success.
873 * Needs to be free'd via rtHttpServerReqFree().
874 */
875static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq,
876 PRTHTTPSERVERREQ *ppReq)
877{
878 RT_NOREF(pClient);
879
880 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
881 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
882
883 /* We only support UTF-8 charset for now. */
884 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
885
886 /** Advances pszReq to the next string in the request.
887 ** @todo Can we do better here? */
888#define REQ_GET_NEXT_STRING \
889 pszReq = psz; \
890 while (psz && !RT_C_IS_SPACE(*psz)) \
891 psz++; \
892 if (psz) \
893 { \
894 *psz = '\0'; \
895 psz++; \
896 } \
897 else /* Be strict for now. */ \
898 AssertFailedReturn(VERR_INVALID_PARAMETER);
899
900 char *psz = pszReq;
901
902 /* Make sure to terminate the string in any case. */
903 psz[RT_MIN(RTHTTPSERVER_MAX_REQ_LEN - 1, cbReq)] = '\0';
904
905 /* A tiny bit of sanitation. */
906 RTStrStrip(psz);
907
908 PRTHTTPSERVERREQ pReq = rtHttpServerReqAlloc();
909 AssertPtrReturn(pReq, VERR_NO_MEMORY);
910
911 REQ_GET_NEXT_STRING
912
913 /* Note: Method names are case sensitive. */
914 if (!RTStrCmp(pszReq, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
915 else if (!RTStrCmp(pszReq, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
916 else
917 return VERR_NOT_SUPPORTED;
918
919 REQ_GET_NEXT_STRING
920
921 pReq->pszUrl = RTStrDup(pszReq);
922
923 if (!rtHttpServerPathIsValid(pReq->pszUrl, false /* fIsAbsolute */))
924 return VERR_PATH_NOT_FOUND;
925
926 REQ_GET_NEXT_STRING
927
928 /* Only HTTP 1.1 is supported by now. */
929 if (RTStrCmp(pszReq, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
930 return VERR_NOT_SUPPORTED;
931
932 int rc = rtHttpServerParseHeaders(pReq->hHdrLst, pszReq, cbReq);
933 if (RT_SUCCESS(rc))
934 *ppReq = pReq;
935
936 return rc;
937}
938
939/**
940 * Main function for processing client requests.
941 *
942 * @returns VBox status code.
943 * @param pClient Client to process request for.
944 * @param pszReq Request string to parse and handle.
945 * @param cbReq Size (in bytes) of request string.
946 */
947static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
948{
949 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
950
951 PRTHTTPSERVERREQ pReq = NULL; /* Shut up GCC. */
952 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
953 if (RT_SUCCESS(rc))
954 {
955 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
956
957 unsigned i = 0;
958 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
959 {
960 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
961 if (pReq->enmMethod == pMethodEntry->enmMethod)
962 {
963 /* Hand in request to method handler. */
964 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
965 if (RT_FAILURE(rcMethod))
966 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
967
968 enmSts = rtHttpServerRcToStatus(rcMethod);
969 break;
970 }
971 }
972
973 if (i == RT_ELEMENTS(g_aMethodMap))
974 enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
975
976 rtHttpServerReqFree(pReq);
977 }
978 else
979 enmSts = RTHTTPSTATUS_BADREQUEST;
980
981 if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET)
982 {
983 int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
984 if (RT_SUCCESS(rc))
985 rc = rc2;
986 }
987
988 LogFlowFuncLeaveRC(rc);
989 return rc;
990}
991
992/**
993 * Main loop for processing client requests.
994 *
995 * @returns VBox status code.
996 * @param pClient Client to process requests for.
997 */
998static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient)
999{
1000 int rc;
1001
1002 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
1003
1004 LogFlowFunc(("Client connected\n"));
1005
1006 rc = RTTcpSelectOne(pClient->hSocket, RT_INDEFINITE_WAIT);
1007 if (RT_SUCCESS(rc))
1008 {
1009 char *pszReq = szReq;
1010 size_t cbRead;
1011 size_t cbToRead = sizeof(szReq);
1012 size_t cbReadTotal = 0;
1013
1014 do
1015 {
1016 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
1017 if (RT_FAILURE(rc))
1018 break;
1019
1020 if (!cbRead)
1021 break;
1022
1023 /* Make sure to terminate string read so far. */
1024 pszReq[cbRead] = '\0';
1025
1026 /* End of request reached? */
1027 /** @todo BUGBUG Improve this. */
1028 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
1029 if (pszEOR)
1030 {
1031 cbReadTotal = pszEOR - pszReq;
1032 *pszEOR = '\0';
1033 break;
1034 }
1035
1036 AssertBreak(cbToRead >= cbRead);
1037 cbToRead -= cbRead;
1038 cbReadTotal += cbRead;
1039 AssertBreak(cbReadTotal <= sizeof(szReq));
1040 pszReq += cbRead;
1041
1042 } while (cbToRead);
1043
1044 if ( RT_SUCCESS(rc)
1045 && cbReadTotal)
1046 {
1047 LogFlowFunc(("Received client request (%zu bytes)\n", cbReadTotal));
1048
1049 rtHttpServerLogProto(pClient, false /* fWrite */, szReq);
1050
1051 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
1052 }
1053 }
1054
1055 LogFlowFuncLeaveRC(rc);
1056 return rc;
1057}
1058
1059/**
1060 * Per-client thread for serving the server's control connection.
1061 *
1062 * @returns VBox status code.
1063 * @param hSocket Socket handle to use for the control connection.
1064 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
1065 */
1066static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
1067{
1068 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
1069 RTHTTPSERVER_VALID_RETURN(pThis);
1070
1071 LogFlowFuncEnter();
1072
1073 RTHTTPSERVERCLIENT Client;
1074 RT_ZERO(Client);
1075
1076 Client.pServer = pThis;
1077 Client.hSocket = hSocket;
1078
1079 return rtHttpServerClientMain(&Client);
1080}
1081
1082RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
1083 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1084{
1085 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
1086 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
1087 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1088 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1089 /* pvUser is optional. */
1090
1091 int rc;
1092
1093 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
1094 if (pThis)
1095 {
1096 pThis->u32Magic = RTHTTPSERVER_MAGIC;
1097 pThis->Callbacks = *pCallbacks;
1098 pThis->pvUser = pvUser;
1099 pThis->cbUser = cbUser;
1100
1101 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
1102 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1103 if (RT_SUCCESS(rc))
1104 {
1105 *hHttpServer = (RTHTTPSERVER)pThis;
1106 }
1107 }
1108 else
1109 rc = VERR_NO_MEMORY;
1110
1111 return rc;
1112}
1113
1114RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
1115{
1116 if (hHttpServer == NIL_RTHTTPSERVER)
1117 return VINF_SUCCESS;
1118
1119 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
1120 RTHTTPSERVER_VALID_RETURN(pThis);
1121
1122 AssertPtr(pThis->pTCPServer);
1123
1124 int rc = RTTcpServerDestroy(pThis->pTCPServer);
1125 if (RT_SUCCESS(rc))
1126 {
1127 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
1128
1129 RTMemFree(pThis);
1130 }
1131
1132 return rc;
1133}
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