/** * * @file HttpRequest.cc * An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by a MIT license * that can be found in the License file. * * Drogon * */ #include "http_request.h" #include "http_file_upload_request.h" //#include "HttpAppFrameworkImpl.h" #include #include "core/application.h" #include #include #ifndef _WIN32 #include #endif using namespace drogon; void HttpRequest::parseJson() const { auto input = contentView(); if (input.empty()) return; if (contentType_ == CT_APPLICATION_JSON || getHeaderBy("content-type").find("application/json") != std::string::npos) { static std::once_flag once; static Json::CharReaderBuilder builder; std::call_once(once, []() { builder["collectComments"] = false; }); jsonPtr_ = std::make_shared(); JSONCPP_STRING errs; std::unique_ptr reader(builder.newCharReader()); if (!reader->parse(input.data(), input.data() + input.size(), jsonPtr_.get(), &errs)) { LOG_ERROR << errs; jsonPtr_.reset(); jsonParsingErrorPtr_ = std::make_unique(std::move(errs)); } else { jsonParsingErrorPtr_.reset(); } } else { jsonPtr_.reset(); jsonParsingErrorPtr_ = std::make_unique("content type error"); } } void HttpRequest::parseParameters() const { auto input = queryView(); if (!input.empty()) { string_view::size_type pos = 0; while ((input[pos] == '?' || isspace(input[pos])) && pos < input.length()) { ++pos; } auto value = input.substr(pos); while ((pos = value.find('&')) != string_view::npos) { auto coo = value.substr(0, pos); auto epos = coo.find('='); if (epos != string_view::npos) { auto key = coo.substr(0, epos); string_view::size_type cpos = 0; while (cpos < key.length() && isspace(key[cpos])) ++cpos; key = key.substr(cpos); auto pvalue = coo.substr(epos + 1); std::string pdecode = utils::urlDecode(pvalue); std::string keydecode = utils::urlDecode(key); parameters_[keydecode] = pdecode; } value = value.substr(pos + 1); } if (value.length() > 0) { auto &coo = value; auto epos = coo.find('='); if (epos != string_view::npos) { auto key = coo.substr(0, epos); string_view::size_type cpos = 0; while (cpos < key.length() && isspace(key[cpos])) ++cpos; key = key.substr(cpos); auto pvalue = coo.substr(epos + 1); std::string pdecode = utils::urlDecode(pvalue); std::string keydecode = utils::urlDecode(key); parameters_[keydecode] = pdecode; } } } input = contentView(); if (input.empty()) return; std::string type = getHeaderBy("content-type"); std::transform(type.begin(), type.end(), type.begin(), tolower); if (type.empty() || type.find("application/x-www-form-urlencoded") != std::string::npos) { string_view::size_type pos = 0; while ((input[pos] == '?' || isspace(input[pos])) && pos < input.length()) { ++pos; } auto value = input.substr(pos); while ((pos = value.find('&')) != string_view::npos) { auto coo = value.substr(0, pos); auto epos = coo.find('='); if (epos != string_view::npos) { auto key = coo.substr(0, epos); string_view::size_type cpos = 0; while (cpos < key.length() && isspace(key[cpos])) ++cpos; key = key.substr(cpos); auto pvalue = coo.substr(epos + 1); std::string pdecode = utils::urlDecode(pvalue); std::string keydecode = utils::urlDecode(key); parameters_[keydecode] = pdecode; } value = value.substr(pos + 1); } if (value.length() > 0) { auto &coo = value; auto epos = coo.find('='); if (epos != string_view::npos) { auto key = coo.substr(0, epos); string_view::size_type cpos = 0; while (cpos < key.length() && isspace(key[cpos])) ++cpos; key = key.substr(cpos); auto pvalue = coo.substr(epos + 1); std::string pdecode = utils::urlDecode(pvalue); std::string keydecode = utils::urlDecode(key); parameters_[keydecode] = pdecode; } } } } void HttpRequest::appendToBuffer(trantor::MsgBuffer *output) const { switch (method_) { case Get: output->append("GET "); break; case Post: output->append("POST "); break; case Head: output->append("HEAD "); break; case Put: output->append("PUT "); break; case Delete: output->append("DELETE "); break; case Options: output->append("OPTIONS "); break; case Patch: output->append("PATCH "); break; default: return; } if (!path_.empty()) { output->append(utils::urlEncode(path_)); } else { output->append("/"); } std::string content; if (!passThrough_ && (!parameters_.empty() && contentType_ != CT_MULTIPART_FORM_DATA)) { for (auto const &p : parameters_) { content.append(utils::urlEncodeComponent(p.first)); content.append("="); content.append(utils::urlEncodeComponent(p.second)); content.append("&"); } content.resize(content.length() - 1); if (method_ == Get || method_ == Delete || method_ == Head) { auto ret = std::find(output->peek(), (const char *)output->beginWrite(), '?'); if (ret != output->beginWrite()) { if (ret != output->beginWrite() - 1) { output->append("&"); } } else { output->append("?"); } output->append(content); content.clear(); } else if (contentType_ == CT_APPLICATION_JSON) { /// Can't set parameters in content in this case LOG_ERROR << "You can't set parameters in the query string when the " "request content type is JSON and http method " "is POST or PUT"; LOG_ERROR << "Please put these parameters into the path or " "into the json " "string"; content.clear(); } } output->append(" "); if (version_ == Version::kHttp11) { output->append("HTTP/1.1"); } else if (version_ == Version::kHttp10) { output->append("HTTP/1.0"); } else { return; } output->append("\r\n"); if (!passThrough_ && contentType_ == CT_MULTIPART_FORM_DATA) { auto mReq = dynamic_cast(this); if (mReq) { for (auto ¶m : mReq->getParameters()) { content.append("--"); content.append(mReq->boundary()); content.append("\r\n"); content.append("Content-Disposition: form-data; name=\""); content.append(param.first); content.append("\"\r\n\r\n"); content.append(param.second); content.append("\r\n"); } for (auto &file : mReq->files()) { content.append("--"); content.append(mReq->boundary()); content.append("\r\n"); content.append("Content-Disposition: form-data; name=\""); content.append(file.itemName()); content.append("\"; filename=\""); content.append(file.fileName()); content.append("\"\r\n\r\n"); std::ifstream infile(file.path(), std::ifstream::binary); if (!infile) { LOG_ERROR << file.path() << " not found"; } else { std::streambuf *pbuf = infile.rdbuf(); std::streamsize filesize = pbuf->pubseekoff(0, infile.end); pbuf->pubseekoff(0, infile.beg); // rewind std::string str; str.resize(filesize); pbuf->sgetn(&str[0], filesize); content.append(std::move(str)); } content.append("\r\n"); } content.append("--"); content.append(mReq->boundary()); content.append("--"); } } assert(!(!content.empty() && !content_.empty())); if (!passThrough_ && (!content.empty() || !content_.empty())) { char buf[64]; auto len = snprintf(buf, sizeof(buf), contentLengthFormatString(), content.length() + content_.length()); output->append(buf, len); if (contentTypeString_.empty()) { auto &type = webContentTypeToString(contentType_); output->append(type.data(), type.length()); } } if (!passThrough_ && !contentTypeString_.empty()) { output->append(contentTypeString_); } for (auto it = headers_.begin(); it != headers_.end(); ++it) { output->append(it->first); output->append(": "); output->append(it->second); output->append("\r\n"); } if (cookies_.size() > 0) { output->append("Cookie: "); for (auto it = cookies_.begin(); it != cookies_.end(); ++it) { output->append(it->first); output->append("= "); output->append(it->second); output->append(";"); } output->unwrite(1); // delete last ';' output->append("\r\n"); } output->append("\r\n"); if (!content.empty()) output->append(content); if (!content_.empty()) output->append(content_); } void HttpRequest::addHeader(const char *start, const char *colon, const char *end) { std::string field(start, colon); // Field name is case-insensitive.so we transform it to lower;(rfc2616-4.2) std::transform(field.begin(), field.end(), field.begin(), ::tolower); ++colon; while (colon < end && isspace(*colon)) { ++colon; } std::string value(colon, end); while (!value.empty() && isspace(value[value.size() - 1])) { value.resize(value.size() - 1); } if (field.length() == 6 && field == "cookie") { LOG_TRACE << "cookies!!!:" << value; std::string::size_type pos; while ((pos = value.find(';')) != std::string::npos) { std::string coo = value.substr(0, pos); auto epos = coo.find('='); if (epos != std::string::npos) { std::string cookie_name = coo.substr(0, epos); std::string::size_type cpos = 0; while (cpos < cookie_name.length() && isspace(cookie_name[cpos])) ++cpos; cookie_name = cookie_name.substr(cpos); std::string cookie_value = coo.substr(epos + 1); cookies_[std::move(cookie_name)] = std::move(cookie_value); } value = value.substr(pos + 1); } if (value.length() > 0) { std::string &coo = value; auto epos = coo.find('='); if (epos != std::string::npos) { std::string cookie_name = coo.substr(0, epos); std::string::size_type cpos = 0; while (cpos < cookie_name.length() && isspace(cookie_name[cpos])) ++cpos; cookie_name = cookie_name.substr(cpos); std::string cookie_value = coo.substr(epos + 1); cookies_[std::move(cookie_name)] = std::move(cookie_value); } } } else { switch (field.length()) { case 6: if (field == "expect") { expectPtr_ = std::make_unique(value); } break; case 10: { if (field == "connection") { if (version_ == Version::kHttp11) { if (value.length() == 5 && value == "close") keepAlive_ = false; } else if (value.length() == 10 && (value == "Keep-Alive" || value == "keep-alive")) { keepAlive_ = true; } } } break; default: break; } headers_.emplace(std::move(field), std::move(value)); } } HttpRequestPtr HttpRequest::newHttpRequest() { auto req = std::make_shared(nullptr); req->setMethod(drogon::Get); req->setVersion(drogon::Version::kHttp11); return req; } HttpRequestPtr HttpRequest::newHttpFormPostRequest() { auto req = std::make_shared(nullptr); req->setMethod(drogon::Post); req->setVersion(drogon::Version::kHttp11); req->contentType_ = CT_APPLICATION_X_FORM; req->flagForParsingContentType_ = true; return req; } HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data) { static std::once_flag once; static Json::StreamWriterBuilder builder; std::call_once(once, []() { builder["commentStyle"] = "None"; builder["indentation"] = ""; /* if (!Application::get_instance()->isUnicodeEscapingUsedInJson()) { builder["emitUTF8"] = true; }*/ }); auto req = std::make_shared(nullptr); req->setMethod(drogon::Get); req->setVersion(drogon::Version::kHttp11); req->contentType_ = CT_APPLICATION_JSON; req->setContent(writeString(builder, data)); req->flagForParsingContentType_ = true; return req; } HttpRequestPtr HttpRequest::newFileUploadRequest( const std::vector &files) { return std::make_shared(files); } void HttpRequest::swap(HttpRequest &that) noexcept { using std::swap; swap(method_, that.method_); swap(version_, that.version_); swap(flagForParsingJson_, that.flagForParsingJson_); swap(flagForParsingParameters_, that.flagForParsingParameters_); swap(matchedPathPattern_, that.matchedPathPattern_); swap(path_, that.path_); swap(query_, that.query_); swap(headers_, that.headers_); swap(cookies_, that.cookies_); swap(parameters_, that.parameters_); swap(jsonPtr_, that.jsonPtr_); swap(sessionPtr_, that.sessionPtr_); swap(attributesPtr_, that.attributesPtr_); swap(cacheFilePtr_, that.cacheFilePtr_); swap(peer_, that.peer_); swap(local_, that.local_); swap(creationDate_, that.creationDate_); swap(content_, that.content_); swap(expectPtr_, that.expectPtr_); swap(contentType_, that.contentType_); swap(contentTypeString_, that.contentTypeString_); swap(keepAlive_, that.keepAlive_); swap(loop_, that.loop_); swap(flagForParsingContentType_, that.flagForParsingContentType_); swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_); } const char *HttpRequest::methodString() const { const char *result = "UNKNOWN"; switch (method_) { case Get: result = "GET"; break; case Post: result = "POST"; break; case Head: result = "HEAD"; break; case Put: result = "PUT"; break; case Delete: result = "DELETE"; break; case Options: result = "OPTIONS"; break; case Patch: result = "PATCH"; break; default: break; } return result; } bool HttpRequest::setMethod(const char *start, const char *end) { assert(method_ == Invalid); string_view m(start, end - start); switch (m.length()) { case 3: if (m == "GET") { method_ = Get; } else if (m == "PUT") { method_ = Put; } else { method_ = Invalid; } break; case 4: if (m == "POST") { method_ = Post; } else if (m == "HEAD") { method_ = Head; } else { method_ = Invalid; } break; case 5: if (m == "PATCH") { method_ = Patch; } else { method_ = Invalid; } break; case 6: if (m == "DELETE") { method_ = Delete; } else { method_ = Invalid; } break; case 7: if (m == "OPTIONS") { method_ = Options; } else { method_ = Invalid; } break; default: method_ = Invalid; break; } // if (method_ != Invalid) // { // content_ = ""; // query_ = ""; // cookies_.clear(); // parameters_.clear(); // headers_.clear(); // } return method_ != Invalid; } HttpRequest::~HttpRequest() { } void HttpRequest::reserveBodySize(size_t length) { if (length <= Application::get_instance()->getClientMaxMemoryBodySize()) { content_.reserve(length); } else { // Store data of body to a temperary file createTmpFile(); } } void HttpRequest::appendToBody(const char *data, size_t length) { if (cacheFilePtr_) { cacheFilePtr_->append(data, length); } else { if (content_.length() + length <= Application::get_instance()->getClientMaxMemoryBodySize()) { content_.append(data, length); } else { createTmpFile(); cacheFilePtr_->append(content_); cacheFilePtr_->append(data, length); content_.clear(); } } } void HttpRequest::createTmpFile() { /* auto tmpfile = Application::get_instance()->getUploadPath(); auto fileName = utils::getUuid(); tmpfile.append("/tmp/") .append(1, fileName[0]) .append(1, fileName[1]) .append("/") .append(fileName); cacheFilePtr_ = std::make_unique(tmpfile); */ }