diff --git a/src/server/kiwix-serve.cpp b/src/server/kiwix-serve.cpp index 232adaf..ca76026 100644 --- a/src/server/kiwix-serve.cpp +++ b/src/server/kiwix-serve.cpp @@ -81,6 +81,8 @@ extern "C" { #include #endif +#include "request_context.h" + #ifdef interface #undef interface #endif @@ -124,35 +126,11 @@ static std::string getMimeTypeForFile(const std::string& filename) } -struct RequestContext { - struct MHD_Connection* connection; - int httpResponseCode; - kiwix::Reader* reader; - kiwix::Searcher* searcher; - const std::string urlStr; - const std::string humanReadableBookId; - bool acceptEncodingDeflate; - bool acceptRange; - int range_start; - int range_end; - - RequestContext(struct MHD_Connection* connection, int httpResponseCode, - kiwix::Reader* reader, kiwix::Searcher* searcher, - const std::string& urlStr, const std::string& humanReadableBookId, - bool acceptEncodingDeflate, - bool acceptRange, int range_start, int range_end) : - connection(connection), - httpResponseCode(httpResponseCode), - reader(reader), - searcher(searcher), - urlStr(urlStr), - humanReadableBookId(humanReadableBookId), - acceptEncodingDeflate(acceptEncodingDeflate), - acceptRange(acceptRange), - range_start(range_start), - range_end(range_end) - {} -}; +static bool startswith(const std::string& base, const std::string& start) +{ + return start.length() <= base.length() + && std::equal(start.begin(), start.end(), base.begin()); +} void introduceTaskbar(string& content, const string& humanReadableBookId) @@ -285,22 +263,34 @@ static struct MHD_Response* build_response(const void* data, } -static struct MHD_Response* build_404(RequestContext* request_context) { +static struct MHD_Response* build_404(RequestContext* request, + const std::string& humanReadableBookId) { std::string content = "\nContent not found

Not " "Found

The requested URL \"" - + request_context->urlStr + "\" was not found on this server.

"; + + request->get_full_url() + "\" was not found on this server.

"; auto mimeType = "text/html"; - request_context->httpResponseCode = MHD_HTTP_NOT_FOUND; - introduceTaskbar(content, request_context->humanReadableBookId); + request->httpResponseCode = MHD_HTTP_NOT_FOUND; + introduceTaskbar(content, humanReadableBookId); bool deflated - = request_context->acceptEncodingDeflate && compress_content(content, mimeType); + = request->can_compress() && compress_content(content, mimeType); return build_response( content.data(), content.size(), "", mimeType, deflated, false); } +static struct MHD_Response* build_homepage(RequestContext* request) +{ + std::string content = welcomeHTML; + + std::string mimeType = "text/html; charset=utf-8"; + + bool deflated = request->can_compress() && compress_content(content, mimeType); + return build_response( + content.data(), content.size(), "", mimeType, deflated, false); +} + struct RunningResponse { zim::Article* article; int range_start; @@ -380,29 +370,54 @@ static struct MHD_Response* build_callback_response_from_article( return response; } -static struct MHD_Response* handle_suggest(RequestContext* request_context) +std::pair +get_from_humanReadableBookId(const std::string& humanReadableBookId) { + kiwix::Searcher* searcher + = searchers.find(humanReadableBookId) != searchers.end() + ? searchers.find(humanReadableBookId)->second + : globalSearcher; + kiwix::Reader* reader = readers.find(humanReadableBookId) != readers.end() + ? readers.find(humanReadableBookId)->second + : NULL; + return std::pair(reader, searcher); +} + +static struct MHD_Response* handle_suggest(RequestContext* request) { + if (isVerbose.load()) { + printf("** running handle_suggest\n"); + } + std::string content; std::string mimeType; unsigned int maxSuggestionCount = 10; unsigned int suggestionCount = 0; std::string suggestion; - /* Get the suggestion pattern from the HTTP request */ - const char* cTerm = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "term"); - std::string term = cTerm == NULL ? "" : cTerm; + std::string humanReadableBookId; + std::string term; + try { + humanReadableBookId = request->get_argument("content"); + term = request->get_argument("term"); + } catch (const std::out_of_range&) { + return build_homepage(request); + } + if (isVerbose.load()) { printf("Searching suggestions for: \"%s\"\n", term.c_str()); } + auto reader_searcher = get_from_humanReadableBookId(humanReadableBookId); + auto reader = reader_searcher.first; + auto searcher = reader_searcher.second; + pthread_mutex_lock(&searchLock); /* Get the suggestions */ content = "["; - if (request_context->reader != NULL) { + if (reader != nullptr) { /* Get the suggestions */ - request_context->reader->searchSuggestionsSmart(term, maxSuggestionCount); - while (request_context->reader->getNextSuggestion(suggestion)) { + reader->searchSuggestionsSmart(term, maxSuggestionCount); + while (reader->getNextSuggestion(suggestion)) { kiwix::stringReplacement(suggestion, "\"", "\\\""); content += (content == "[" ? "" : ","); content += "{\"value\":\"" + suggestion + "\",\"label\":\"" + suggestion @@ -413,126 +428,136 @@ static struct MHD_Response* handle_suggest(RequestContext* request_context) pthread_mutex_unlock(&searchLock); /* Propose the fulltext search if possible */ - if (request_context->searcher != NULL) { + if (searcher != NULL) { content += (suggestionCount == 0 ? "" : ","); - content += "{\"value\":\"" + std::string(term) - + " \", \"label\":\"containing '" + std::string(term) + content += "{\"value\":\"" + term + + " \", \"label\":\"containing '" + term + "'...\"}"; } content += "]"; mimeType = "application/json; charset=utf-8"; - bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType); + bool deflated = request->can_compress() && compress_content(content, mimeType); return build_response( content.data(), content.size(), "", mimeType, deflated, true); } -static struct MHD_Response* handle_skin(RequestContext* request_context) +static struct MHD_Response* handle_skin(RequestContext* request) { - std::string content; - try { - content = getResource(request_context->urlStr.substr(rootLocation.size() + 6)); - } catch (const ResourceNotFound& e) { - return build_404(request_context); + if (isVerbose.load()) { + printf("** running handle_skin\n"); } - std::string mimeType = getMimeTypeForFile(request_context->urlStr); - bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType); + + std::string content; + auto resourceName = request->get_url().substr(6); + try { + content = getResource(resourceName); + } catch (const ResourceNotFound& e) { + return build_404(request, ""); + } + std::string mimeType = getMimeTypeForFile(resourceName); + bool deflated = request->can_compress() && compress_content(content, mimeType); return build_response( content.data(), content.size(), "", mimeType, deflated, true); } -static struct MHD_Response* handle_search(RequestContext* request_context) +static struct MHD_Response* handle_search(RequestContext* request) { + if (isVerbose.load()) { + printf("** running handle_search\n"); + } + std::string content; std::string mimeType; std::string httpRedirection; - /* Retrieve the pattern to search */ - const char* pattern = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "pattern"); - std::string patternString - = kiwix::urlDecode(pattern == NULL ? "" : string(pattern)); - + std::string humanReadableBookId; + std::string patternString; + try { + humanReadableBookId = request->get_argument("content"); + patternString = request->get_argument("pattern"); + } catch (const std::out_of_range&) { + return build_homepage(request); + } /* Retrive geo search */ bool has_geo_query = false; - const char* latitude = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "latitude"); - const char* longitude = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "longitude"); - const char* distance = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "distance"); - - float latitudeFloat(0), longitudeFloat(0), distanceFloat(0); - if (latitude != nullptr && longitude != nullptr && distance != nullptr) { - try { - latitudeFloat = stof(string(latitude)); - longitudeFloat = stof(string(longitude)); - distanceFloat = stof(string(distance)); - has_geo_query = true; - } catch (...) {} - } + float latitude; + float longitude; + float distance; + try { + latitude = request->get_argument("latitude"); + longitude = request->get_argument("longitude"); + distance = request->get_argument("distance"); + has_geo_query = true; + } catch(const std::out_of_range&) {} + catch(const std::invalid_argument&) {} /* Search results for searches from the welcome page should not be cached */ - bool cacheEnabled = !(request_context->searcher == globalSearcher); - - std::string patternCorrespondingUrl; + auto reader_searcher = get_from_humanReadableBookId(humanReadableBookId); + auto reader = reader_searcher.first; + auto searcher = reader_searcher.second; + bool cacheEnabled = !(searcher == globalSearcher); /* Try first to load directly the article */ - if (request_context->reader != NULL) { - std::vector variants = request_context->reader->getTitleVariants(patternString); - std::vector::iterator variantsItr = variants.begin(); + if (reader != nullptr) { + std::string patternCorrespondingUrl; + auto variants = reader->getTitleVariants(patternString); + auto variantsItr = variants.begin(); while (patternCorrespondingUrl.empty() && variantsItr != variants.end()) { - request_context->reader->getPageUrlFromTitle(*variantsItr, patternCorrespondingUrl); + reader->getPageUrlFromTitle(*variantsItr, patternCorrespondingUrl); variantsItr++; } /* If article found then redirect directly to it */ if (!patternCorrespondingUrl.empty()) { httpRedirection - = rootLocation + "/" + request_context->humanReadableBookId + "/" + patternCorrespondingUrl; - request_context->httpResponseCode = MHD_HTTP_FOUND; + = rootLocation + "/" + humanReadableBookId + "/" + patternCorrespondingUrl; + request->httpResponseCode = MHD_HTTP_FOUND; return build_response("", 0, httpRedirection, "", false, true); } } /* Make the search */ - if (request_context->searcher != NULL) { - const char* start = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "start"); - const char* end = MHD_lookup_connection_value( - request_context->connection, MHD_GET_ARGUMENT_KIND, "end"); - unsigned int startNumber = start != NULL ? atoi(start) : 0; - unsigned int endNumber = end != NULL ? atoi(end) : 25; + if (reader_searcher.second != nullptr && + (!patternString.empty() || has_geo_query)) { + auto start = 0; + try { + start = request->get_argument("start"); + } catch (const std::exception&) {} + auto end = 25; + try { + end = request->get_argument("end"); + } catch (const std::exception&) {} /* Get the results */ pthread_mutex_lock(&searchLock); try { - if (patternString.empty() && has_geo_query) { - request_context->searcher->geo_search(latitudeFloat, longitudeFloat, distanceFloat, - startNumber, endNumber, isVerbose.load()); + if (patternString.empty()) { + searcher->geo_search(latitude, longitude, distance, + start, end, isVerbose.load()); } else { - request_context->searcher->search(patternString, - startNumber, endNumber, isVerbose.load()); + searcher->search(patternString, + start, end, isVerbose.load()); } - content = request_context->searcher->getHtml(); + content = searcher->getHtml(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } pthread_mutex_unlock(&searchLock); } else { content = "\nFulltext search unavailable

Not Found

There is no article with the title \"" + kiwix::encodeDiples(patternString) + "\" and the fulltext search engine is not available for this content.

"; - request_context->httpResponseCode = MHD_HTTP_NOT_FOUND; + request->httpResponseCode = MHD_HTTP_NOT_FOUND; } mimeType = "text/html; charset=utf-8"; - introduceTaskbar(content, request_context->humanReadableBookId); + introduceTaskbar(content, humanReadableBookId); - bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType); + bool deflated = request->can_compress() && compress_content(content, mimeType); return build_response(content.data(), content.size(), httpRedirection, @@ -541,28 +566,61 @@ static struct MHD_Response* handle_search(RequestContext* request_context) cacheEnabled); } -static struct MHD_Response* handle_random(RequestContext* request_context) +static struct MHD_Response* handle_random(RequestContext* request) { - std::string httpRedirection; - request_context->httpResponseCode = MHD_HTTP_FOUND; - if (request_context->reader != NULL) { - std::string randomUrl = request_context->reader->getRandomPageUrl(); - httpRedirection - = rootLocation + "/" + request_context->humanReadableBookId + "/" + kiwix::urlEncode(randomUrl); + if (isVerbose.load()) { + printf("** running handle_random\n"); } + + std::string httpRedirection; + request->httpResponseCode = MHD_HTTP_FOUND; + std::string humanReadableBookId; + try { + humanReadableBookId = request->get_argument("content"); + } catch (const std::out_of_range&) { + return build_homepage(request); + } + + auto reader = get_from_humanReadableBookId(humanReadableBookId).first; + if (reader == nullptr) { + return build_homepage(request); + } + + std::string randomUrl = reader->getRandomPageUrl(); + httpRedirection + = rootLocation + "/" + humanReadableBookId + "/" + kiwix::urlEncode(randomUrl); return build_response("", 0, httpRedirection, "", false, false); } -static struct MHD_Response* handle_content(RequestContext* request_context) +static struct MHD_Response* handle_content(RequestContext* request) { + if (isVerbose.load()) { + printf("** running handle_content\n"); + } + std::string baseUrl; std::string content; std::string mimeType; bool found = false; zim::Article article; + + std::string humanReadableBookId; try { - found = request_context->reader->getArticleObjectByDecodedUrl(request_context->urlStr, article); + humanReadableBookId = request->get_url_part(0); + } catch (const std::out_of_range& e) { + return build_homepage(request); + } + + auto reader = get_from_humanReadableBookId(humanReadableBookId).first; + if (reader == nullptr) { + return build_homepage(request); + } + + auto urlStr = request->get_url().substr(humanReadableBookId.size()+1); + + try { + found = reader->getArticleObjectByDecodedUrl(urlStr, article); if (found) { /* If redirect */ @@ -582,9 +640,9 @@ static struct MHD_Response* handle_content(RequestContext* request_context) if (!found) { if (isVerbose.load()) - printf("Failed to find %s\n", request_context->urlStr.c_str()); + printf("Failed to find %s\n", urlStr.c_str()); - return build_404(request_context); + return build_404(request, humanReadableBookId); } try { @@ -594,7 +652,7 @@ static struct MHD_Response* handle_content(RequestContext* request_context) } if (isVerbose.load()) { - printf("Found %s\n", request_context->urlStr.c_str()); + printf("Found %s\n", urlStr.c_str()); printf("mimeType: %s\n", mimeType.c_str()); } @@ -611,59 +669,48 @@ static struct MHD_Response* handle_content(RequestContext* request_context) + article.getUrl(); pthread_mutex_lock(®exLock); content = replaceRegex(content, - "$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/", + "$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/", "(href|src)(=[\"|\']{0,1})/([A-Z|\\-])/"); content = replaceRegex(content, - "$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/", + "$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/", "(@import[ ]+)([\"|\']{0,1})/([A-Z|\\-])/"); content = replaceRegex( content, - "humanReadableBookId + baseUrl + "\" />", + "", ""); pthread_mutex_unlock(®exLock); - introduceTaskbar(content, request_context->humanReadableBookId); + introduceTaskbar(content, humanReadableBookId); } else if (mimeType.find("text/css") != string::npos) { pthread_mutex_lock(®exLock); content = replaceRegex(content, - "$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/", + "$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/", "(url|URL)(\\([\"|\']{0,1})/([A-Z|\\-])/"); pthread_mutex_unlock(®exLock); } bool deflated - = request_context->acceptEncodingDeflate && compress_content(content, mimeType); + = request->can_compress() && compress_content(content, mimeType); return build_response( content.data(), content.size(), "", mimeType, deflated, true); } else { int range_len; - if (request_context->range_end == -1) { - range_len = article.getArticleSize() - request_context->range_start; + if (request->get_range().second == -1) { + range_len = article.getArticleSize() - request->get_range().first; } else { - range_len = request_context->range_end - request_context->range_start; + range_len = request->get_range().second - request->get_range().first; } return build_callback_response_from_article( article, - request_context->range_start, + request->get_range().first, range_len, mimeType); } } -static struct MHD_Response* handle_default(RequestContext* request_context) -{ - std::string content = welcomeHTML; - - std::string mimeType = "text/html; charset=utf-8"; - - bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType); - return build_response( - content.data(), content.size(), "", mimeType, deflated, false); -} - -int print_out_key (void *cls, enum MHD_ValueKind kind, +int print_key_value (void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { - printf ("%s: %s\n", key, value); + printf (" - %s: '%s'\n", key, value); return MHD_YES; } @@ -676,138 +723,46 @@ static int accessHandlerCallback(void* cls, size_t* upload_data_size, void** ptr) { + RequestContext request(connection, rootLocation, url, method, version); /* Unexpected method */ - if (0 != strcmp(method, "GET") && 0 != strcmp(method, "POST")) + if (request.get_method() != RequestMethod::GET && request.get_method() != RequestMethod::POST) { return MHD_NO; - - if (isVerbose.load()) { - printf("Requesting : \n"); - printf("u : %s\n", url); - printf("m : %s\n", method); - printf("v : %s\n", version); - MHD_get_connection_values (connection, MHD_HEADER_KIND, &print_out_key, NULL); } - /* Check if the response can be compressed */ - const char* acceptEncodingHeaderValue = MHD_lookup_connection_value( - connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT_ENCODING); - const bool acceptEncodingDeflate - = acceptEncodingHeaderValue - && string(acceptEncodingHeaderValue).find("deflate") != string::npos; - - /* Check if range is requested. */ - const char* acceptRangeHeaderValue = MHD_lookup_connection_value( - connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE); - bool acceptRange = false; - int range_start = 0; - int range_end = -1; - if (acceptRangeHeaderValue != NULL) { - // [FIXME] This part is sub-optimal and potentially prone to fail - // because we don't check the string length before using substr - // The `range.length() >= 6` should mitigate the bug but we have to - // rewrite this part. - auto range = std::string(acceptRangeHeaderValue); - if (range.length() >= 6 && range.substr(0, 6) == "bytes=") { - range = range.substr(6); - std::istringstream iss(range); - iss >> range_start; - range = range.substr(iss.tellg()); - if (range[0] == '-') { - range = range.substr(1); - if (! range.empty()) { - std::istringstream iss(range); - iss >> range_end; - range = range.substr(iss.tellg()); - } - if (range.empty()) { - // Nothing left to read. We are OK. - acceptRange = true; - } - } - } + if (isVerbose.load()) { + printf("======================\n"); + request.print_debug_info(); } /* Prepare the variables */ struct MHD_Response* response; - int httpResponseCode = acceptRange ? MHD_HTTP_PARTIAL_CONTENT : MHD_HTTP_OK; - std::string urlStr = string(url); + request.httpResponseCode = request.has_range() ? MHD_HTTP_PARTIAL_CONTENT : MHD_HTTP_OK; - /* Get searcher and reader */ - std::string humanReadableBookId = ""; - - if (!rootLocation.empty() && urlStr.substr(0, rootLocation.size() + 1) != rootLocation + "/"){ - humanReadableBookId = ""; - } - - else if (!(urlStr.size() > rootLocation.size() + 5 && urlStr.substr(rootLocation.size() , 6) == "/skin/")) { - if ((urlStr == rootLocation + "/" + "search") || (urlStr == rootLocation + "/" + "suggest") - || (urlStr == rootLocation + "/" + "random")) { - const char* tmpGetValue = MHD_lookup_connection_value( - connection, MHD_GET_ARGUMENT_KIND, "content"); - humanReadableBookId = (tmpGetValue != NULL ? string(tmpGetValue) : ""); + if (! request.is_valid_url()) { + response = build_homepage(&request); + } else { + if (startswith(request.get_url(), "/skin/")) { + response = handle_skin(&request); + } else if (request.get_url() == "/search") { + response = handle_search(&request); + } else if (request.get_url() == "/suggest") { + response = handle_suggest(&request); + } else if (request.get_url() == "/random") { + response = handle_random(&request); } else { - humanReadableBookId = urlStr.substr(rootLocation.size() + 1, - urlStr.find("/", rootLocation.size() + 1) != string::npos - ? urlStr.find("/", rootLocation.size() + 1) - (rootLocation.size() + 1) - : urlStr.size() - (rootLocation.size() + 2)); - if (!humanReadableBookId.empty()) { - urlStr = urlStr.substr(urlStr.find("/", rootLocation.size() + 1) != string::npos - ? urlStr.find("/", rootLocation.size() + 1) - : humanReadableBookId.size()); - } + response = handle_content(&request); } } - kiwix::Searcher* searcher - = searchers.find(humanReadableBookId) != searchers.end() - ? searchers.find(humanReadableBookId)->second - : globalSearcher; - kiwix::Reader* reader = readers.find(humanReadableBookId) != readers.end() - ? readers.find(humanReadableBookId)->second - : NULL; - if (reader == NULL) { - humanReadableBookId = ""; - } - - RequestContext request_context(connection, httpResponseCode, - reader, searcher, - urlStr, humanReadableBookId, - acceptEncodingDeflate, - acceptRange, range_start, range_end); - - - /* Get suggestions */ - if ((urlStr == (rootLocation + "/" + "suggest")) && reader != NULL) { - response = handle_suggest(&request_context); - } - - /* Get static skin stuff */ - else if (urlStr.size() > rootLocation.size() + 5 && urlStr.substr(rootLocation.size() , 6) == "/skin/") { - response = handle_skin(&request_context); - } - - /* Display the search restults */ - else if (urlStr == (rootLocation + "/" + "search")) { - response = handle_search(&request_context); - } - - /* Display a random article */ - else if (urlStr == (rootLocation + "/" + "random")) { - response = handle_random(&request_context); - } - - /* Display the content of a ZIM content (article, image, ...) */ - else if (reader != NULL) { - response = handle_content(&request_context); - } - - /* Display the global Welcome page */ - else { - response = handle_default(&request_context); - } - /* Queue the response */ - int ret = MHD_queue_response(connection, request_context.httpResponseCode, response); + if (isVerbose.load()) { + printf("Response :\n"); + printf("httpResponseCode : %d\n", request.httpResponseCode); + printf("headers :\n"); + MHD_get_response_headers(response, print_key_value, nullptr); + printf("----------------------\n"); + } + int ret = MHD_queue_response(connection, request.httpResponseCode, response); MHD_destroy_response(response); return ret; diff --git a/src/server/meson.build b/src/server/meson.build index 78bfa97..f337656 100644 --- a/src/server/meson.build +++ b/src/server/meson.build @@ -1,5 +1,5 @@ -sources = ['kiwix-serve.cpp'] +sources = ['kiwix-serve.cpp', 'request_context.cpp'] sources += server_resources executable('kiwix-serve', sources, diff --git a/src/server/request_context.cpp b/src/server/request_context.cpp new file mode 100644 index 0000000..9caa1dc --- /dev/null +++ b/src/server/request_context.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2009-2016 Emmanuel Engelhart + * Copyright 2017 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + + +#include "request_context.h" +#include +#include +#include + +RequestContext::RequestContext(struct MHD_Connection* connection, + std::string rootLocation, + const std::string& _url, + const std::string& method, + const std::string& version) : + connection(connection), + full_url(_url), + url(_url), + valid_url(true), + version(version), + acceptEncodingDeflate(false), + accept_range(false), + range_pair(0, -1) +{ + if (method == "GET") { + this->method = RequestMethod::GET; + } else if (method == "HEAD") { + this->method = RequestMethod::HEAD; + } else if (method == "POST") { + this->method = RequestMethod::POST; + } else if (method == "PUT") { + this->method = RequestMethod::PUT; + } else if (method == "DELETE") { + this->method = RequestMethod::DELETE_; + } else if (method == "CONNECT") { + this->method = RequestMethod::CONNECT; + } else if (method == "OPTIONS") { + this->method = RequestMethod::OPTIONS; + } else if (method == "TRACE") { + this->method = RequestMethod::TRACE; + } else if (method == "PATCH") { + this->method = RequestMethod::PATCH; + } else { + this->method = RequestMethod::OTHER; + } + + MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this); + MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this); + + valid_url = true; + if (rootLocation.empty()) { + // nothing special to handle. + url = full_url; + } else { + if (full_url.size() > rootLocation.size() && + full_url.substr(0, rootLocation.size()+1) == rootLocation + "/") { + url = full_url.substr(rootLocation.size()); + } else { + valid_url = false; + } + } + + try { + acceptEncodingDeflate = + (get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("deflate") != std::string::npos); + } catch (const std::out_of_range&) {} + + /*Check if range is requested. */ + try { + auto range = get_header(MHD_HTTP_HEADER_RANGE); + int start = 0; + int end = -1; + std::istringstream iss(range); + char c; + + iss >> start >> c; + if (iss.good() && c=='-') { + iss >> end; + if (iss.fail()) { + // Something went wrong will extracting. + end = -1; + } + if (iss.eof()) { + accept_range = true; + range_pair = std::pair(start, end); + } + } + } catch (const std::out_of_range&) {} + + +} + +RequestContext::~RequestContext() +{} + + +int RequestContext::fill_header(void *__this, enum MHD_ValueKind kind, + const char *key, const char *value) +{ + RequestContext *_this = static_cast(__this); + _this->headers[key] = value; + return MHD_YES; +} + +int RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind, + const char *key, const char* value) +{ + RequestContext *_this = static_cast(__this); + _this->arguments[key] = value; + return MHD_YES; +} + +void RequestContext::print_debug_info() { + printf("Requesting : \n"); + printf("full_url : %s\n", full_url.c_str()); + printf("method : %s (%d)\n", method==RequestMethod::GET ? "GET" : + method==RequestMethod::POST ? "POST" : + "OTHER", method); + printf("version : %s\n", version.c_str()); + printf("headers :\n"); + for (auto it=headers.begin(); it!=headers.end(); it++) { + printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str()); + } + printf("arguments :\n"); + for (auto it=arguments.begin(); it!=arguments.end(); it++) { + printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str()); + } + printf("Parsed : \n"); + printf("url : %s\n", url.c_str()); + printf("acceptEncodingDeflate : %d\n", acceptEncodingDeflate); + printf("has_range : %d\n", accept_range); + printf(".............\n"); +} + + +RequestMethod RequestContext::get_method() { + return method; +} + +std::string RequestContext::get_url() { + return url; +} + +std::string RequestContext::get_url_part(int number) { + size_t start = 1; + while(true) { + auto found = url.find('/', start); + if (number == 0) { + if (found == std::string::npos) { + return url.substr(start); + } else { + return url.substr(start, found-start); + } + } else { + if (found == std::string::npos) { + throw std::out_of_range("No parts"); + } + start = found + 1; + number -= 1; + } + } +} + +std::string RequestContext::get_full_url() { + return full_url; +} + +bool RequestContext::is_valid_url() { + return valid_url; +} + +bool RequestContext::has_range() { + return accept_range; +} + +std::pair RequestContext::get_range() { + return range_pair; +} + +template<> +std::string RequestContext::get_argument(const std::string& name) { + return arguments.at(name); +} + +template<> +unsigned int RequestContext::get_argument(const std::string& name) { + return std::stoi(arguments.at(name).c_str()); +} + +template<> +float RequestContext::get_argument(const std::string& name) { + return std::stof(arguments.at(name).c_str()); +} + +std::string RequestContext::get_header(const std::string& name) { + return headers.at(name); +} diff --git a/src/server/request_context.h b/src/server/request_context.h new file mode 100644 index 0000000..687fd84 --- /dev/null +++ b/src/server/request_context.h @@ -0,0 +1,100 @@ +/* + * Copyright 2009-2016 Emmanuel Engelhart + * Copyright 2017 Matthieu Gautier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + + +#ifndef REQUEST_CONTEXT_H +#define REQUEST_CONTEXT_H + +#include +#include +#include + +extern "C" { +#include +} + +enum class RequestMethod { + GET, + HEAD, + POST, + PUT, + DELETE_, + CONNECT, + OPTIONS, + TRACE, + PATCH, + OTHER +}; + +class KeyError : public std::runtime_error {}; +class IndexError: public std::runtime_error {}; + + +class RequestContext { + public: + RequestContext(struct MHD_Connection* connection, + std::string rootLocation, + const std::string& url, + const std::string& method, + const std::string& version); + ~RequestContext(); + + void print_debug_info(); + + bool is_valid_url(); + + std::string get_header(const std::string& name); + template + T get_argument(const std::string& name); + + RequestMethod get_method(); + std::string get_url(); + std::string get_url_part(int part); + std::string get_full_url(); + + bool has_range(); + std::pair get_range(); + + bool can_compress() { return acceptEncodingDeflate; } + + // [TODO] Move this to the response builder + int httpResponseCode; + + private: + struct MHD_Connection* connection; + std::string full_url; + std::string url; + bool valid_url; + RequestMethod method; + std::string version; + + bool acceptEncodingDeflate; + + bool accept_range; + std::pair range_pair; + std::map headers; + std::map arguments; + + static int fill_header(void *, enum MHD_ValueKind, const char*, const char*); + static int fill_argument(void *, enum MHD_ValueKind, const char*, const char*); +}; + + +#endif //REQUEST_CONTEXT_H