You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.1 KiB
153 lines
5.1 KiB
/**
|
|
* Digital Voice Modem - Host Software
|
|
* GPLv2 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* @package DVM / Host Software
|
|
*
|
|
*/
|
|
//
|
|
// Based on code from the CRUD project. (https://github.com/venediktov/CRUD)
|
|
// Licensed under the BPL-1.0 License (https://opensource.org/license/bsl1-0-html)
|
|
//
|
|
/*
|
|
* Copyright (c) 2003-2013 Christopher M. Kohlhoff
|
|
* Copyright (C) 2023 by Bryan Biedenkapp N2PLL
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person or organization
|
|
* obtaining a copy of the software and accompanying documentation covered by
|
|
* this license (the “Software”) to use, reproduce, display, distribute, execute,
|
|
* and transmit the Software, and to prepare derivative works of the Software, and
|
|
* to permit third-parties to whom the Software is furnished to do so, all subject
|
|
* to the following:
|
|
*
|
|
* The copyright notices in the Software and this entire statement, including the
|
|
* above license grant, this restriction and the following disclaimer, must be included
|
|
* in all copies of the Software, in whole or in part, and all derivative works of the
|
|
* Software, unless such copies or derivative works are solely in the form of
|
|
* machine-executable object code generated by a source language processor.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
* PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
|
|
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include "Defines.h"
|
|
#include "network/rest/http/HTTPRequestHandler.h"
|
|
#include "network/rest/http/HTTPRequest.h"
|
|
#include "network/rest/http/HTTPReply.h"
|
|
|
|
using namespace rest::server;
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public Class Members
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the HTTPRequestHandler class.
|
|
/// </summary>
|
|
/// <param name="docRoot"></param>
|
|
HTTPRequestHandler::HTTPRequestHandler(const std::string& docRoot) :
|
|
m_docRoot(docRoot)
|
|
{
|
|
/* stub */
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle a request and produce a reply.
|
|
/// </summary>
|
|
void HTTPRequestHandler::handleRequest(const HTTPRequest& request, HTTPReply& reply)
|
|
{
|
|
// decode url to path
|
|
std::string requestPath;
|
|
if (!urlDecode(request.uri, requestPath)) {
|
|
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
|
|
return;
|
|
}
|
|
|
|
// request path must be absolute and not contain "..".
|
|
if (requestPath.empty() || requestPath[0] != '/' ||
|
|
requestPath.find("..") != std::string::npos) {
|
|
reply = HTTPReply::stockReply(HTTPReply::BAD_REQUEST);
|
|
return;
|
|
}
|
|
|
|
// if path ends in slash (i.e. is a directory) then add "index.html"
|
|
if (requestPath[requestPath.size() - 1] == '/') {
|
|
requestPath += "index.html";
|
|
}
|
|
|
|
// determine the file extension
|
|
std::size_t lastSlashPos = requestPath.find_last_of("/");
|
|
std::size_t lastDotPos = requestPath.find_last_of(".");
|
|
std::string extension;
|
|
if (lastDotPos != std::string::npos && lastDotPos > lastSlashPos) {
|
|
extension = requestPath.substr(lastDotPos + 1);
|
|
}
|
|
|
|
// open the file to send back
|
|
std::string fullPath = m_docRoot + requestPath;
|
|
std::ifstream is(fullPath.c_str(), std::ios::in | std::ios::binary);
|
|
if (!is) {
|
|
reply = HTTPReply::stockReply(HTTPReply::NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
// fill out the reply to be sent to the client
|
|
reply.status = HTTPReply::OK;
|
|
|
|
char buf[512];
|
|
while (is.read(buf, sizeof(buf)).gcount() > 0)
|
|
reply.content.append(buf, is.gcount());
|
|
|
|
reply.headers.resize(2);
|
|
reply.headers[0].name = "Content-Length";
|
|
reply.headers[0].value = std::to_string(reply.content.size());
|
|
reply.headers[1].name = "Content-Type";
|
|
reply.headers[1].value = "application/octet-stream";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform URL-decoding on a string. Returns false if the encoding was invalid.
|
|
/// </summary>
|
|
/// <param name="in"></param>
|
|
/// <param name="out"></param>
|
|
bool HTTPRequestHandler::urlDecode(const std::string& in, std::string& out)
|
|
{
|
|
out.clear();
|
|
out.reserve(in.size());
|
|
|
|
for (std::size_t i = 0; i < in.size(); ++i) {
|
|
if (in[i] == '%') {
|
|
if (i + 3 <= in.size()) {
|
|
int value = 0;
|
|
std::istringstream is(in.substr(i + 1, 2));
|
|
if (is >> std::hex >> value) {
|
|
out += static_cast<char>(value);
|
|
i += 2;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else if (in[i] == '+') {
|
|
out += ' ';
|
|
}
|
|
else {
|
|
out += in[i];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|