Handles an individual client connection.
329{
330
331 struct timeval stTimeVal;
332 stTimeVal.tv_sec = 5;
333 stTimeVal.tv_usec = 0;
334 setsockopt(nClientFD, SOL_SOCKET, SO_RCVTIMEO, (const char*) &stTimeVal, sizeof stTimeVal);
335
336
337 std::vector<char> vRawRequest;
338 char aChunk[1024];
339 bool bHeaderFound = false;
340
341
342 while (!bHeaderFound && m_bRunning)
343 {
344
345 ssize_t siBytes = recv(nClientFD, aChunk, sizeof(aChunk), 0);
346
347 if (siBytes <= 0)
348 {
349 break;
350 }
351
352 vRawRequest.insert(vRawRequest.end(), aChunk, aChunk + siBytes);
353
354
355 std::string szCurrent(vRawRequest.begin(), vRawRequest.end());
356
357 if (szCurrent.find("\r\n\r\n") != std::string::npos)
358 {
359 bHeaderFound = true;
360 }
361
362
363 if (vRawRequest.size() > 16384)
364 {
365 break;
366 }
367 }
368
369
370 if (bHeaderFound && m_bRunning)
371 {
372
373 std::string szRequest(vRawRequest.begin(), vRawRequest.end());
374 std::string szMethod, szPath, szQuery;
375 std::istringstream stdISS(szRequest);
376 stdISS >> szMethod >> szPath;
377
378
379 size_t siQPos = szPath.find('?');
380
381 if (siQPos != std::string::npos)
382 {
383 szQuery = szPath.substr(siQPos + 1);
384 szPath = szPath.substr(0, siQPos);
385 }
386
387
388 RequestCallback fnCallback = nullptr;
389 std::string szStaticFileToServe;
390 std::string szHtmlCopy;
391
392 {
393
394 std::lock_guard<std::mutex> lkDataLock(m_muDataMutex);
395
396 if (m_mGetCallbacks.count(szPath))
397 {
398
399 fnCallback = m_mGetCallbacks[szPath];
400 }
401 else
402 {
403 for (const std::pair<const std::string, StaticDir>& stPair : m_mStaticDirectories)
404 {
405
406
407 if (szPath.starts_with(stPair.first))
408 {
409
410
411 std::string szSubPath = szPath.substr(stPair.first.length());
412
413
414 if (szSubPath.empty() || szSubPath == "/")
415 {
416
417 break;
418 }
419
420
421 if (szSubPath[0] == '/')
422 {
423 szSubPath = szSubPath.substr(1);
424 }
425
426
427 if (szSubPath.find("..") != std::string::npos)
428 {
429 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Path traversal attempt detected: {}", szPath);
430 break;
431 }
432
433
434 std::filesystem::path szLocalPath = std::filesystem::path(stPair.second.szLocalPath) / std::filesystem::path(szSubPath);
435
436
437 std::error_code stdErrorCode;
438 std::filesystem::path szCanonicalBase = std::filesystem::canonical(stPair.second.szLocalPath, stdErrorCode);
439 if (stdErrorCode)
440 {
441 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Failed to canonicalize base path: {}", stPair.second.szLocalPath);
442 break;
443 }
444
445 std::filesystem::path szCanonicalPath = std::filesystem::canonical(szLocalPath, stdErrorCode);
446 if (stdErrorCode)
447 {
448
449 break;
450 }
451
452
453
454 std::string szCanonicalBaseStr = szCanonicalBase.string();
455 std::string szCanonicalPathStr = szCanonicalPath.string();
456 bool bPathWithinBase = (szCanonicalPathStr.find(szCanonicalBaseStr) == 0);
457
458 if (!bPathWithinBase)
459 {
460 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Path traversal attempt blocked: {}", szPath);
461 break;
462 }
463
464 if (std::filesystem::exists(szCanonicalPath) && !std::filesystem::is_directory(szCanonicalPath))
465 {
466 szStaticFileToServe = szCanonicalPath.string();
467 }
468 break;
469 }
470 }
471
472
473 if (!fnCallback && szStaticFileToServe.empty())
474 {
475 szHtmlCopy = m_szHtmlContent;
476 }
477 }
478 }
479
480
481 if (fnCallback)
482 {
483
484 std::vector<char> vData = fnCallback(szQuery);
485
486
487 std::string szContentType = "application/octet-stream";
488 if (szPath.length() >= 3 && szPath.substr(szPath.length() - 3) == ".js")
489 {
490 szContentType = "text/javascript";
491 }
492 else if (szPath.length() >= 4 && szPath.substr(szPath.length() - 4) == ".css")
493 {
494 szContentType = "text/css";
495 }
496
497 std::string szHeader = "HTTP/1.1 200 OK\r\n"
498 "Content-Type: " +
499 szContentType +
500 "\r\n"
501 "Access-Control-Allow-Origin: *\r\n"
502 "Content-Length: " +
503 std::to_string(vData.size()) +
504 "\r\n"
505 "Connection: close\r\n\r\n";
506
507 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
508
509
510 size_t siRemaining = vData.size();
511 size_t siSent = 0;
512 while (siRemaining > 0 && m_bRunning)
513 {
514
515 size_t siChunk = (siRemaining > 65536) ? 65536 : siRemaining;
516 ssize_t result = send(nClientFD, vData.data() + siSent, siChunk, MSG_NOSIGNAL);
517
518 if (result <= 0)
519 {
520 break;
521 }
522
523
524 siSent += result;
525 siRemaining -= result;
526 }
527 }
528
529 else if (szPath == "/" || szPath == "/index.html")
530 {
531
532 std::string szHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " + std::to_string(szHtmlCopy.size()) + "\r\nConnection: close\r\n\r\n";
533 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
534 send(nClientFD, szHtmlCopy.c_str(), szHtmlCopy.size(), MSG_NOSIGNAL);
535 }
536
537 else if (!szStaticFileToServe.empty())
538 {
539
540 std::vector<char> vFileData =
LoadFile(szStaticFileToServe);
541 if (!vFileData.empty())
542 {
543
544 std::string szMimeType =
GetMimeType(szStaticFileToServe);
545
546
547 std::string szHeader = "HTTP/1.1 200 OK\r\n"
548 "Content-Type: " +
549 szMimeType +
550 "\r\n"
551 "Access-Control-Allow-Origin: *\r\n"
552 "Content-Length: " +
553 std::to_string(vFileData.size()) +
554 "\r\n"
555 "Connection: close\r\n\r\n";
556 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
557
558
559 size_t siRemaining = vFileData.size();
560 size_t siSent = 0;
561 while (siRemaining > 0 && m_bRunning)
562 {
563 size_t siChunk = (siRemaining > 65536) ? 65536 : siRemaining;
564 ssize_t result = send(nClientFD, vFileData.data() + siSent, siChunk, MSG_NOSIGNAL);
565 if (result <= 0)
566 {
567 break;
568 }
569 siSent += result;
570 siRemaining -= result;
571 }
572 }
573 else
574 {
575
576 std::string szResp = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
577 send(nClientFD, szResp.c_str(), szResp.size(), MSG_NOSIGNAL);
578 }
579 }
580
581 else
582 {
583
584 std::string szResp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
585 send(nClientFD, szResp.c_str(), szResp.size(), MSG_NOSIGNAL);
586 }
587 }
588
589
590 close(nClientFD);
591}
std::vector< char > LoadFile(const std::string &szPath)
Loads a file from disk into a byte vector.
Definition SimpleWebServer.cpp:119
std::string GetMimeType(const std::string &szPath)
Determines the MIME type based on the file extension.
Definition SimpleWebServer.cpp:165