/**
* 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/HTTPPayload.h"

using namespace network::rest::http;

#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 HTTPPayload& request, HTTPPayload& reply)
{
    // decode url to path
    std::string requestPath;
    if (!urlDecode(request.uri, requestPath)) {
        reply = HTTPPayload::statusPayload(HTTPPayload::BAD_REQUEST);
        return;
    }

    // request path must be absolute and not contain "..".
    if (requestPath.empty() || requestPath[0] != '/' ||
        requestPath.find("..") != std::string::npos) {
        reply = HTTPPayload::statusPayload(HTTPPayload::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 = HTTPPayload::statusPayload(HTTPPayload::NOT_FOUND);
        return;
    }

    // fill out the reply to be sent to the client
    reply.status = HTTPPayload::OK;

    char buf[512];
    while (is.read(buf, sizeof(buf)).gcount() > 0)
        reply.content.append(buf, is.gcount());

    reply.headers.clearHeaders();
    reply.headers.add("Content-Length", std::to_string(reply.content.size()));
    reply.headers.add("Content-Type", "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;
}
