VirtualBox

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

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

Shared Clipboard/Transfers: Initial commit for HTTP server. Work in progress [build fix]. bugref:9874

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.7 KB
Line 
1/* $Id: http-server.cpp 87006 2020-11-27 16:28:54Z 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* 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
298static const char *rtHttpServerGuessMIMEType(const char *pszFileExt)
299{
300 if (pszFileExt)
301 {
302 size_t i = 0;
303 while (s_aFileExtMIMEType[i++].pszExt) /* Slow, but does the job for now. */
304 {
305 if (!RTStrICmp(pszFileExt, s_aFileExtMIMEType[i].pszExt))
306 return s_aFileExtMIMEType[i].pszMIMEType;
307 }
308 }
309
310 return "application/octet-stream";
311}
312
313static PRTHTTPSERVERREQ rtHttpServerReqAlloc(void)
314{
315 PRTHTTPSERVERREQ pReq = (PRTHTTPSERVERREQ)RTMemAllocZ(sizeof(RTHTTPSERVERREQ));
316 AssertPtrReturn(pReq, NULL);
317
318 int rc2 = RTHttpHeaderListInit(&pReq->hHdrLst);
319 AssertRC(rc2);
320
321 return pReq;
322}
323
324static void rtHttpServerReqFree(PRTHTTPSERVERREQ pReq)
325{
326 if (!pReq)
327 return;
328
329 RTHttpHeaderListDestroy(pReq->hHdrLst);
330
331 RTMemFree(pReq->pvBody);
332 pReq->pvBody = NULL;
333
334 RTMemFree(pReq);
335}
336
337
338/*********************************************************************************************************************************
339* Protocol Functions *
340*********************************************************************************************************************************/
341
342static int rtHttpServerSendResponseHdrEx(PRTHTTPSERVERCLIENT pClient,
343 RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
344{
345 char *pszHdr;
346 int rc = RTStrAPrintf(&pszHdr,
347 "%s %RU32 %s\r\n", RTHTTPVER_1_1_STR, enmSts, RTHttpStatusToStr(enmSts));
348 AssertRCReturn(rc, rc);
349
350 RTHTTPHEADERLIST HdrLst;
351 rc = RTHttpHeaderListInit(&HdrLst);
352 AssertRCReturn(rc, rc);
353
354#ifdef DEBUG
355 /* Include a timestamp when running a debug build. */
356 RTTIMESPEC tsNow;
357 char szTS[64];
358 RTTimeSpecToString(RTTimeNow(&tsNow), szTS, sizeof(szTS));
359 rc = RTHttpHeaderListAdd(HdrLst, "Date", szTS, strlen(szTS), RTHTTPHEADERLISTADD_F_BACK);
360 AssertRCReturn(rc, rc);
361#endif
362
363 /* Note: Deliberately don't include the VBox version due to security reasons. */
364 rc = RTHttpHeaderListAdd(HdrLst, "Server", "Oracle VirtualBox", strlen("Oracle VirtualBox"), RTHTTPHEADERLISTADD_F_BACK);
365 AssertRCReturn(rc, rc);
366
367 size_t i = 0;
368 const char *pszEntry;
369 while ((pszEntry = RTHttpHeaderListGetByOrdinal(HdrLst, i++)) != NULL)
370 {
371 rc = RTStrAAppend(&pszHdr, pszEntry);
372 AssertRCBreak(rc);
373 rc = RTStrAAppend(&pszHdr, "\r\n");
374 AssertRCBreak(rc);
375 }
376
377 /* Append optional headers, if any. */
378 if (pHdrLst)
379 {
380 i = 0;
381 while ((pszEntry = RTHttpHeaderListGetByOrdinal(*pHdrLst, i++)) != NULL)
382 {
383 rc = RTStrAAppend(&pszHdr, pszEntry);
384 AssertRCBreak(rc);
385 rc = RTStrAAppend(&pszHdr, "\r\n");
386 AssertRCBreak(rc);
387 }
388 }
389
390 if (RT_SUCCESS(rc))
391 {
392 /* Append trailing EOL. */
393 rc = RTStrAAppend(&pszHdr, "\r\n");
394 if (RT_SUCCESS(rc))
395 rc = RTTcpWrite(pClient->hSocket, pszHdr, strlen(pszHdr));
396 }
397
398 RTStrFree(pszHdr);
399
400 RTHttpHeaderListDestroy(HdrLst);
401
402 LogFlowFunc(("enmStatus=%s, rc=%Rrc\n", RTHttpStatusToStr(enmSts), rc));
403 return rc;
404}
405
406/**
407 * Replies with (three digit) response status back to the client.
408 *
409 * @returns VBox status code.
410 * @param pClient Client to reply to.
411 * @param enmStatus Status code to send.
412 * @param pHdrLst Header list to send. Optional and can be NULL.
413 */
414static int rtHttpServerSendResponseEx(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts, PRTHTTPHEADERLIST pHdrLst)
415{
416 int rc = rtHttpServerSendResponseHdrEx(pClient, enmSts, pHdrLst);
417
418 return rc;
419}
420
421static int rtHttpServerSendResponseSimple(PRTHTTPSERVERCLIENT pClient, RTHTTPSTATUS enmSts)
422{
423 return rtHttpServerSendResponseEx(pClient, enmSts, NULL /* pHdrLst */);
424}
425
426static int rtHttpServerSendResponseBody(PRTHTTPSERVERCLIENT pClient, void *pvBuf, size_t cbBuf, size_t *pcbSent)
427{
428 int rc = RTTcpWrite(pClient->hSocket, pvBuf, cbBuf);
429 if ( RT_SUCCESS(rc)
430 && pcbSent)
431 *pcbSent = cbBuf;
432
433 return rc;
434}
435
436static RTHTTPSTATUS rtHttpServerRcToStatus(int rc)
437{
438 switch (rc)
439 {
440 case VINF_SUCCESS: return RTHTTPSTATUS_OK;
441 case VERR_INVALID_PARAMETER: return RTHTTPSTATUS_BADREQUEST;
442 case VERR_INVALID_POINTER: return RTHTTPSTATUS_BADREQUEST;
443 case VERR_NOT_IMPLEMENTED: return RTHTTPSTATUS_NOTIMPLEMENTED;
444 case VERR_PATH_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
445 case VERR_FILE_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
446 case VERR_IS_A_DIRECTORY: return RTHTTPSTATUS_FORBIDDEN;
447 case VERR_NOT_FOUND: return RTHTTPSTATUS_NOTFOUND;
448 default:
449 break;
450 }
451
452 AssertMsgFailed(("rc=%Rrc not handled for HTTP status\n", rc));
453 return RTHTTPSTATUS_INTERNALSERVERERROR;
454}
455
456
457/*********************************************************************************************************************************
458* Command Protocol Handlers *
459*********************************************************************************************************************************/
460
461static DECLCALLBACK(int) rtHttpServerHandleGET(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
462{
463 LogFlowFuncEnter();
464
465 int rc;
466
467 RTFSOBJINFO fsObj;
468 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj);
469 if (RT_FAILURE(rc))
470 return rc;
471
472 uint64_t uID;
473 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnOpen, pReq->pszUrl, &uID);
474
475 if (RT_SUCCESS(rc))
476 {
477 size_t cbBuf = _64K;
478 void *pvBuf = RTMemAlloc(cbBuf);
479 AssertPtrReturn(pvBuf, VERR_NO_MEMORY);
480
481 for (;;)
482 {
483 RTHTTPHEADERLIST HdrLst;
484 rc = RTHttpHeaderListInit(&HdrLst);
485 AssertRCReturn(rc, rc);
486
487 char szVal[16];
488
489 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
490 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
491 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
492 AssertRCBreak(rc);
493
494 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
495 AssertBreakStmt(cch, VERR_BUFFER_OVERFLOW);
496 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
497 AssertRCBreak(rc);
498
499 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
500 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
501 AssertRCReturn(rc, rc);
502
503 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
504 AssertRCReturn(rc, rc);
505
506 RTHttpHeaderListDestroy(HdrLst);
507
508 size_t cbToRead = fsObj.cbObject;
509 size_t cbRead = 0; /* Shut up GCC. */
510 size_t cbWritten = 0; /* Ditto. */
511 while (cbToRead)
512 {
513 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnRead, uID, pvBuf, RT_MIN(cbBuf, cbToRead), &cbRead);
514 if (RT_FAILURE(rc))
515 break;
516 rc = rtHttpServerSendResponseBody(pClient, pvBuf, cbRead, &cbWritten);
517 AssertBreak(cbToRead >= cbWritten);
518 cbToRead -= cbWritten;
519 if (rc == VERR_NET_CONNECTION_RESET_BY_PEER) /* Clients often apruptly abort the connection when done. */
520 {
521 rc = VINF_SUCCESS;
522 break;
523 }
524 AssertRCBreak(rc);
525 }
526
527 break;
528 } /* for (;;) */
529
530 RTMemFree(pvBuf);
531
532 int rc2 = rc; /* Save rc. */
533
534 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnClose, uID);
535
536 if (RT_FAILURE(rc2)) /* Restore original rc on failure. */
537 rc = rc2;
538 }
539
540 LogFlowFuncLeaveRC(rc);
541 return rc;
542}
543
544static DECLCALLBACK(int) rtHttpServerHandleHEAD(PRTHTTPSERVERCLIENT pClient, PRTHTTPSERVERREQ pReq)
545{
546 LogFlowFuncEnter();
547
548 int rc;
549
550 RTFSOBJINFO fsObj;
551 RTHTTPSERVER_HANDLE_CALLBACK_VA(pfnQueryInfo, pReq->pszUrl, &fsObj);
552 if (RT_SUCCESS(rc))
553 {
554 RTHTTPHEADERLIST HdrLst;
555 rc = RTHttpHeaderListInit(&HdrLst);
556 AssertRCReturn(rc, rc);
557
558 /*
559 * Note: A response to a HEAD request does not have a body.
560 * All entity headers below are assumed to describe the the response a similar GET
561 * request would return (but then with a body).
562 */
563 char szVal[16];
564
565 ssize_t cch = RTStrPrintf2(szVal, sizeof(szVal), "%RU64", fsObj.cbObject);
566 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
567 rc = RTHttpHeaderListAdd(HdrLst, "Content-Length", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
568 AssertRCReturn(rc, rc);
569
570 cch = RTStrPrintf2(szVal, sizeof(szVal), "identity");
571 AssertReturn(cch, VERR_BUFFER_OVERFLOW);
572 rc = RTHttpHeaderListAdd(HdrLst, "Content-Encoding", szVal, strlen(szVal), RTHTTPHEADERLISTADD_F_BACK);
573 AssertRCReturn(rc, rc);
574
575 const char *pszMIME = rtHttpServerGuessMIMEType(RTPathSuffix(pReq->pszUrl));
576 rc = RTHttpHeaderListAdd(HdrLst, "Content-Type", pszMIME, strlen(pszMIME), RTHTTPHEADERLISTADD_F_BACK);
577 AssertRCReturn(rc, rc);
578
579 rc = rtHttpServerSendResponseEx(pClient, RTHTTPSTATUS_OK, &HdrLst);
580 AssertRCReturn(rc, rc);
581
582 RTHttpHeaderListDestroy(HdrLst);
583 }
584
585 LogFlowFuncLeaveRC(rc);
586 return rc;
587}
588
589/**
590 * Validates if a given path is valid or not.
591 *
592 * @returns \c true if path is valid, or \c false if not.
593 * @param pszPath Path to check.
594 * @param fIsAbsolute Whether the path to check is an absolute path or not.
595 */
596static bool rtHttpServerPathIsValid(const char *pszPath, bool fIsAbsolute)
597{
598 if (!pszPath)
599 return false;
600
601 bool fIsValid = strlen(pszPath)
602 && RTStrIsValidEncoding(pszPath)
603 && RTStrStr(pszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
604 if ( fIsValid
605 && fIsAbsolute)
606 {
607 RTFSOBJINFO objInfo;
608 int rc2 = RTPathQueryInfo(pszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
609 if (RT_SUCCESS(rc2))
610 {
611 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
612 || RTFS_IS_FILE(objInfo.Attr.fMode);
613
614 /* No symlinks and other stuff not allowed. */
615 }
616 else
617 fIsValid = false;
618 }
619
620 LogFlowFunc(("pszPath=%s -> %RTbool\n", pszPath, fIsValid));
621 return fIsValid;
622
623}
624
625static int rtHttpServerParseHeaders(RTHTTPHEADERLIST hList, char *pszReq, size_t cbReq)
626{
627 /* Nothing to parse left? Bail out early. */
628 if ( !pszReq
629 || !cbReq)
630 return VINF_SUCCESS;
631
632 /* Nothing to do here yet. */
633 RT_NOREF(hList);
634
635 return VINF_SUCCESS;
636}
637
638static int rtHttpServerParseRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq,
639 PRTHTTPSERVERREQ *ppReq)
640{
641 RT_NOREF(pClient);
642
643 AssertPtrReturn(pszReq, VERR_INVALID_POINTER);
644 AssertReturn(cbReq, VERR_INVALID_PARAMETER);
645
646 /* We only support UTF-8 charset for now. */
647 AssertReturn(RTStrIsValidEncoding(pszReq), VERR_INVALID_PARAMETER);
648
649 /** Advances pszReq to the next string in the request.
650 ** @todo Can we do better here? */
651#define REQ_GET_NEXT_STRING \
652 pszReq = psz; \
653 while (psz && !RT_C_IS_SPACE(*psz)) \
654 psz++; \
655 if (psz) \
656 { \
657 *psz = '\0'; \
658 psz++; \
659 } \
660 else /* Be strict for now. */ \
661 AssertFailedReturn(VERR_INVALID_PARAMETER);
662
663 char *psz = pszReq;
664
665 /* Make sure to terminate the string in any case. */
666 psz[RT_MIN(RTHTTPSERVER_MAX_REQ_LEN - 1, cbReq)] = '\0';
667
668 /* A tiny bit of sanitation. */
669 RTStrStrip(psz);
670
671 PRTHTTPSERVERREQ pReq = rtHttpServerReqAlloc();
672 AssertPtrReturn(pReq, VERR_NO_MEMORY);
673
674 REQ_GET_NEXT_STRING
675
676 /* Note: Method names are case sensitive. */
677 if (!RTStrCmp(pszReq, "GET")) pReq->enmMethod = RTHTTPMETHOD_GET;
678 else if (!RTStrCmp(pszReq, "HEAD")) pReq->enmMethod = RTHTTPMETHOD_HEAD;
679 else
680 return VERR_NOT_SUPPORTED;
681
682 REQ_GET_NEXT_STRING
683
684 pReq->pszUrl = RTStrDup(pszReq);
685
686 if (!rtHttpServerPathIsValid(pReq->pszUrl, false /* fIsAbsolute */))
687 return VERR_PATH_NOT_FOUND;
688
689 REQ_GET_NEXT_STRING
690
691 /* Only HTTP 1.1 is supported by now. */
692 if (RTStrCmp(pszReq, RTHTTPVER_1_1_STR)) /** @todo Use RTStrVersionCompare. Later. */
693 return VERR_NOT_SUPPORTED;
694
695 int rc = rtHttpServerParseHeaders(pReq->hHdrLst, pszReq, cbReq);
696 if (RT_SUCCESS(rc))
697 *ppReq = pReq;
698
699 return rc;
700}
701
702/**
703 * Main function for processing client requests.
704 *
705 * @returns VBox status code.
706 * @param pClient Client to process request for.
707 * @param pszReq Request string to parse and handle.
708 * @param cbReq Size (in bytes) of request string.
709 */
710static int rtHttpServerProcessRequest(PRTHTTPSERVERCLIENT pClient, char *pszReq, size_t cbReq)
711{
712 RTHTTPSTATUS enmSts = RTHTTPSTATUS_INTERNAL_NOT_SET;
713
714 PRTHTTPSERVERREQ pReq;
715 int rc = rtHttpServerParseRequest(pClient, pszReq, cbReq, &pReq);
716 if (RT_SUCCESS(rc))
717 {
718 LogFlowFunc(("Request %s %s\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl));
719
720 unsigned i = 0;
721 for (; i < RT_ELEMENTS(g_aMethodMap); i++)
722 {
723 const RTHTTPSERVERMETHOD_ENTRY *pMethodEntry = &g_aMethodMap[i];
724 if (pReq->enmMethod == pMethodEntry->enmMethod)
725 {
726 /* Hand in request to method handler. */
727 int rcMethod = pMethodEntry->pfnMethod(pClient, pReq);
728 if (RT_FAILURE(rcMethod))
729 LogFunc(("Request %s %s failed with %Rrc\n", RTHttpMethodToStr(pReq->enmMethod), pReq->pszUrl, rcMethod));
730
731 enmSts = rtHttpServerRcToStatus(rcMethod);
732 break;
733 }
734 }
735
736 if (i == RT_ELEMENTS(g_aMethodMap))
737 enmSts = RTHTTPSTATUS_NOTIMPLEMENTED;
738
739 rtHttpServerReqFree(pReq);
740 }
741 else
742 enmSts = RTHTTPSTATUS_BADREQUEST;
743
744 if (enmSts != RTHTTPSTATUS_INTERNAL_NOT_SET)
745 {
746 int rc2 = rtHttpServerSendResponseSimple(pClient, enmSts);
747 if (RT_SUCCESS(rc))
748 rc = rc2;
749 }
750
751 LogFlowFuncLeaveRC(rc);
752 return rc;
753}
754
755/**
756 * Main loop for processing client requests.
757 *
758 * @returns VBox status code.
759 * @param pClient Client to process requests for.
760 */
761static int rtHttpServerClientMain(PRTHTTPSERVERCLIENT pClient)
762{
763 int rc;
764
765 char szReq[RTHTTPSERVER_MAX_REQ_LEN + 1];
766
767 LogFlowFunc(("Client connected\n"));
768
769 rc = RTTcpSelectOne(pClient->hSocket, RT_INDEFINITE_WAIT);
770 if (RT_SUCCESS(rc))
771 {
772 char *pszReq = szReq;
773 size_t cbRead;
774 size_t cbToRead = sizeof(szReq);
775 size_t cbReadTotal = 0;
776
777 do
778 {
779 rc = RTTcpReadNB(pClient->hSocket, pszReq, cbToRead, &cbRead);
780 if (RT_FAILURE(rc))
781 break;
782
783 if (!cbRead)
784 break;
785
786 /* Make sure to terminate string read so far. */
787 pszReq[cbRead] = '\0';
788
789 /* End of request reached? */
790 /** @todo BUGBUG Improve this. */
791 char *pszEOR = RTStrStr(&pszReq[cbReadTotal], "\r\n\r\n");
792 if (pszEOR)
793 {
794 cbReadTotal = pszEOR - pszReq;
795 *pszEOR = '\0';
796 break;
797 }
798
799 AssertBreak(cbToRead >= cbRead);
800 cbToRead -= cbRead;
801 cbReadTotal += cbRead;
802 AssertBreak(cbReadTotal <= sizeof(szReq));
803 pszReq += cbRead;
804
805 } while (cbToRead);
806
807 if ( RT_SUCCESS(rc)
808 && cbReadTotal)
809 {
810 LogFlowFunc(("Received request (%zu bytes):\n%s\n\n", cbReadTotal, pszReq));
811
812 rc = rtHttpServerProcessRequest(pClient, szReq, cbReadTotal);
813 }
814 }
815
816 LogFlowFuncLeaveRC(rc);
817 return rc;
818}
819
820/**
821 * Per-client thread for serving the server's control connection.
822 *
823 * @returns VBox status code.
824 * @param hSocket Socket handle to use for the control connection.
825 * @param pvUser User-provided arguments. Of type PRTHTTPSERVERINTERNAL.
826 */
827static DECLCALLBACK(int) rtHttpServerClientThread(RTSOCKET hSocket, void *pvUser)
828{
829 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)pvUser;
830 RTHTTPSERVER_VALID_RETURN(pThis);
831
832 LogFlowFuncEnter();
833
834 RTHTTPSERVERCLIENT Client;
835 RT_ZERO(Client);
836
837 Client.pServer = pThis;
838 Client.hSocket = hSocket;
839
840 return rtHttpServerClientMain(&Client);
841}
842
843RTR3DECL(int) RTHttpServerCreate(PRTHTTPSERVER hHttpServer, const char *pszAddress, uint16_t uPort,
844 PRTHTTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
845{
846 AssertPtrReturn(hHttpServer, VERR_INVALID_POINTER);
847 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
848 AssertReturn (uPort, VERR_INVALID_PARAMETER);
849 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
850 /* pvUser is optional. */
851
852 int rc;
853
854 PRTHTTPSERVERINTERNAL pThis = (PRTHTTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTHTTPSERVERINTERNAL));
855 if (pThis)
856 {
857 pThis->u32Magic = RTHTTPSERVER_MAGIC;
858 pThis->Callbacks = *pCallbacks;
859 pThis->pvUser = pvUser;
860 pThis->cbUser = cbUser;
861
862 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "httpsrv",
863 rtHttpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
864 if (RT_SUCCESS(rc))
865 {
866 *hHttpServer = (RTHTTPSERVER)pThis;
867 }
868 }
869 else
870 rc = VERR_NO_MEMORY;
871
872 return rc;
873}
874
875RTR3DECL(int) RTHttpServerDestroy(RTHTTPSERVER hHttpServer)
876{
877 if (hHttpServer == NIL_RTHTTPSERVER)
878 return VINF_SUCCESS;
879
880 PRTHTTPSERVERINTERNAL pThis = hHttpServer;
881 RTHTTPSERVER_VALID_RETURN(pThis);
882
883 AssertPtr(pThis->pTCPServer);
884
885 int rc = RTTcpServerDestroy(pThis->pTCPServer);
886 if (RT_SUCCESS(rc))
887 {
888 pThis->u32Magic = RTHTTPSERVER_MAGIC_DEAD;
889
890 RTMemFree(pThis);
891 }
892
893 return rc;
894}
895
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