HttpResponse::HttpResponse(TcpClientSocket& socket) : buffer(socket) , ischunked(false) , contentlength(nlen) { raw_statusline = buffer.getline(); if (raw_statusline.empty()) { throw http_exception("Empty response"); } parseResponseLine(raw_statusline); std::vector<std::string> raw_headers; for (std::string line = buffer.getline(); !line.empty(); line = buffer.getline()) { if (('\x20' == line[0]) || ('\t' == line[0])) { auto last = raw_headers.rbegin(); if (raw_headers.rend() == last) { throw http_exception("HTTP request can't start with a whitespace"); } if (last->find(':') == std::string::npos) { throw http_exception("Header name can't reside on multiple lines"); } *last += line; } else { raw_headers.push_back(line); } } parseHeaders(raw_headers); }
void HttpResponse::parseResponseLine(const std::string& reqline) { auto reqline_begin = std::begin(reqline), reqline_end = std::end(reqline), sel_begin = reqline_begin, sel_end = sel_begin; if (sel_end == reqline_end) { throw http_exception("Empty response"); } if ('\x20' == *sel_end) { throw http_exception("Response can't start with whitespace"); } while ((sel_end != reqline_end) && ('\x20' != *sel_end)) { ++sel_end; } if (sel_end == reqline_end) { throw http_exception("Status code is absent"); } version = std::string(sel_begin, sel_end); if ((version != "HTTP/1.0") && (version != "HTTP/1.1")) { throw http_exception("Unsupported HTTP version"); } sel_begin = sel_end; ++sel_begin; sel_end = sel_begin; if (sel_end == reqline_end) { throw http_exception("Status code is absent"); } if ('\x20' == *sel_end) { throw http_exception("Multiple spaces are not allowed in status line"); } while ((sel_end != reqline_end) && ('\x20' != *sel_end)) { ++sel_end; } if (sel_end == reqline_end) { throw http_exception("Reason phrase is absent"); } std::string status_raw = std::string(sel_begin, sel_end); if (!strtonum(status_raw, status) && ((status < 100) || (status > 999))) { throw http_exception("Invalid status"); } sel_begin = sel_end; ++sel_begin; sel_end = reqline_end; reason = std::string(sel_begin, sel_end); }
uri details::_http_request::relative_uri() const { // If the listener path is empty, then just return the request URI. if(m_listener_path.empty() || m_listener_path == _XPLATSTR("/")) { return m_uri.resource(); } utility::string_t prefix = uri::decode(m_listener_path); utility::string_t path = uri::decode(m_uri.resource().to_string()); if(path.empty()) { path = _XPLATSTR("/"); } auto pos = path.find(prefix); if (pos == 0) { return uri(uri::encode_uri(path.erase(0, prefix.length()))); } else { throw http_exception(_XPLATSTR("Error: request was not prefixed with listener uri")); } }
void request_context::report_exception(std::exception_ptr exceptionPtr) { auto response_impl = m_response._get_impl(); // If cancellation has been triggered then ignore any errors. if (m_request._cancellation_token().is_canceled()) { exceptionPtr = std::make_exception_ptr(http_exception((int)std::errc::operation_canceled, std::generic_category())); } // First try to complete the headers with an exception. if (m_request_completion.set_exception(exceptionPtr)) { // Complete the request with no msg body. The exception // should only be propagated to one of the tce. response_impl->_complete(0); } else { // Complete the request with an exception response_impl->_complete(0, exceptionPtr); } finish(); }
pplx::task<void> details::_http_request::reply(const http_response &response) { if(pplx::details::atomic_increment(m_initiated_response) != 1l) { throw http_exception(U("Error: trying to send multiple responses to an HTTP request")); } return _reply_impl(response); }
void HttpResponse::parseHeaders(const std::vector<std::string>& raw_headers) { for (auto it = std::begin(raw_headers), eit = std::end(raw_headers); it != eit; ++it) { size_t colon = it->find(':'); if (std::string::npos == colon) { throw http_exception("Header must have value"); } std::string key(it->begin(), it->begin() + colon), value(it->begin() + (colon + 1), it->end()); canonicalizeHeaderName(key); trim(value); if (value.empty()) { throw http_exception("Header must have value"); } auto& d = headers[key]; d.insert(std::end(d), value); } auto transfer_encoding = headers.find("Transfer-Encoding"); if (std::end(headers) != transfer_encoding) { std::string lastencoding = *transfer_encoding->second.rbegin(); toLower(lastencoding, CP_ISO8859_1); if ("chunked" != lastencoding) { throw http_exception("\"chunked\" must be the last transfer encoding applied to the message"); } ischunked = true; return; } // "Transfer-Encoding: chunked" is not present auto content_length = headers.find("Content-Length"); if (std::end(headers) != content_length) { if (content_length->second.size() > 1) { throw http_exception("Multiple Content-Length headers are not allowed"); } size_t temp; if (!strtonum(*content_length->second.begin(), temp) || nlen == temp) { throw http_exception("Invalid Content-Length"); } contentlength = temp; return; } // Neither "Transfer-Endcoding: chunked" nor valid Content-Length are present auto connection = headers.find("Connection"); if (std::end(headers) != connection) { for (auto it = std::begin(connection->second), eit = std::end(connection->second); it != eit; ++it) { std::string temp = *it; toLower(temp, CP_ISO8859_1); if (temp == "close") { return; } } } // Neither "Transfer-Endcoding: chunked", valid Content-Length, nor "Connection: close" are present -- that's malformed HTTP/1.1 response throw http_exception("Can't obtain message length"); }
virtual void on_post(http_server_context& /*context*/) { BOOST_THROW_EXCEPTION(http_exception(http_status_code::method_not_allowed)); }
void on_connected(socket& /*socket*/, std::iostream& stream) { for (std::size_t keep_alive_count = config_.maximum_keep_alive_requests; keep_alive_count != 0; --keep_alive_count) { stream.exceptions(std::ios_base::badbit); if (stream.peek() == EOF) { stream.clear(); break; } stream.exceptions(std::ios_base::eofbit | std::ios_base::failbit | std::ios_base::badbit); http_server_context context(stream, config_); http_request& req = context.request(); http_response& res = context.response(); bool head = false; bool keep_alive = false; try { context.read_headers(); switch (req.get_method()) { case http_method::head: head = true; break; case http_method::get: case http_method::post: break; default: BOOST_THROW_EXCEPTION(http_exception(http_status_code::method_not_allowed)); } if (req.is_connection_keep_alive()) { res.set_keep_alive_requests(keep_alive_count); keep_alive = true; } context.validate_headers(); if (config_.logger) config_.logger->http_server_request(req); try { switch (req.get_method()) { case http_method::head: case http_method::get: derived().on_get(context, head); break; case http_method::post: derived().on_post(context); break; default: BOOST_THROW_EXCEPTION(http_exception(http_status_code::method_not_allowed)); } } catch(http_exception&) { throw; } catch (interrupted_exception&) { throw; } catch (std::exception&) { BOOST_THROW_EXCEPTION(http_exception(http_status_code::internal_service_error)); } } catch (http_exception& e) { if (config_.logger) config_.logger->exception(); res.set_status_code(e.status_code()); try { auto it = config_.error_document.find(e.status_code()); if (it != config_.error_document.end()) BOOST_THROW_EXCEPTION(http_exception(http_status_code::not_found)); req.set_path(it->second); derived().on_get(context, head); } catch (http_exception&) { null_content().on_get(context, head); } } context.finish_write(); if (!keep_alive) break; } }
void on_get(http_server_context& /*context*/, bool /*head*/) { BOOST_THROW_EXCEPTION(http_exception(http_status_code::method_not_allowed)); }
// Start sending request. void send_request(_In_ const std::shared_ptr<request_context> &request) { http_request &msg = request->m_request; auto winrt_context = std::static_pointer_cast<winrt_request_context>(request); if (!validate_method(msg.method())) { request->report_exception(http_exception(L"The method string is invalid.")); return; } if (msg.method() == http::methods::TRCE) { // Not supported by WinInet. Generate a more specific exception than what WinInet does. request->report_exception(http_exception(L"TRACE is not supported")); return; } const size_t content_length = msg._get_impl()->_get_content_length(); if (content_length == std::numeric_limits<size_t>::max()) { // IXHR2 does not allow transfer encoding chunked. So the user is expected to set the content length request->report_exception(http_exception(L"Content length is not specified in the http headers")); return; } // Start sending HTTP request. HRESULT hr = CoCreateInstance( __uuidof(FreeThreadedXMLHTTP60), nullptr, CLSCTX_INPROC, __uuidof(IXMLHTTPRequest2), reinterpret_cast<void**>(winrt_context->m_hRequest.GetAddressOf())); if (FAILED(hr)) { request->report_error(hr, L"Failure to create IXMLHTTPRequest2 instance"); return; } utility::string_t encoded_resource = http::uri_builder(m_uri).append(msg.relative_uri()).to_string(); const auto &config = client_config(); const auto &client_cred = config.credentials(); const auto &proxy = config.proxy(); const auto &proxy_cred = proxy.credentials(); if (!proxy.is_default()) { request->report_exception(http_exception(L"Only a default proxy server is supported")); return; } // New scope to ensure plain text password is cleared as soon as possible. { utility::string_t username, proxy_username; const utility::char_t *password = nullptr; const utility::char_t *proxy_password = nullptr; ::web::details::plaintext_string password_plaintext, proxy_password_plaintext; if (client_cred.is_set()) { username = client_cred.username(); password_plaintext = client_cred.decrypt(); password = password_plaintext->c_str(); } if (proxy_cred.is_set()) { proxy_username = proxy_cred.username(); proxy_password_plaintext = proxy_cred.decrypt(); proxy_password = proxy_password_plaintext->c_str(); } hr = winrt_context->m_hRequest->Open( msg.method().c_str(), encoded_resource.c_str(), Make<HttpRequestCallback>(winrt_context).Get(), username.c_str(), password, proxy_username.c_str(), proxy_password); } if (FAILED(hr)) { request->report_error(hr, L"Failure to open HTTP request"); return; } // Suppress automatic prompts for user credentials, since they are already provided. hr = winrt_context->m_hRequest->SetProperty(XHR_PROP_NO_CRED_PROMPT, TRUE); if (FAILED(hr)) { request->report_error(hr, L"Failure to set no credentials prompt property"); return; } const auto timeout = config.timeout(); const int secs = static_cast<int>(timeout.count()); hr = winrt_context->m_hRequest->SetProperty(XHR_PROP_TIMEOUT, secs * 1000); if (FAILED(hr)) { request->report_error(hr, L"Failure to set HTTP request properties"); return; } // Add headers. for (const auto &hdr : msg.headers()) { winrt_context->m_hRequest->SetRequestHeader(hdr.first.c_str(), hdr.second.c_str()); } // Set response stream. hr = winrt_context->m_hRequest->SetCustomResponseStream(Make<IResponseStream>(request).Get()); if (FAILED(hr)) { request->report_error(hr, L"Failure to set HTTP response stream"); return; } // Call the callback function of user customized options try { config.call_user_nativehandle_options(winrt_context->m_hRequest.Get()); } catch (...) { request->report_exception(std::current_exception()); return; } if (content_length == 0) { hr = winrt_context->m_hRequest->Send(nullptr, 0); } else { if ( msg.method() == http::methods::GET || msg.method() == http::methods::HEAD ) { request->report_exception(http_exception(get_with_body)); return; } hr = winrt_context->m_hRequest->Send(Make<IRequestStream>(winrt_context, content_length).Get(), content_length); } if ( FAILED(hr) ) { request->report_error(hr, L"Failure to send HTTP request"); return; } // Register for notification on cancellation to abort this request. if(msg._cancellation_token() != pplx::cancellation_token::none()) { auto requestHandle = winrt_context->m_hRequest; // cancellation callback is unregistered when request is completed. winrt_context->m_cancellationRegistration = msg._cancellation_token().register_callback([requestHandle]() { requestHandle->Abort(); }); } }