// Copyright (c) 2018 The Swedish Internet Foundation // Written by Göran Andersson #ifdef _WIN32 #define NOMINMAX #include #else #include #endif #include #include #include #include #include #ifdef _WIN32 #include #include #include #pragma comment(lib, "ws2_32.lib") #else #include #include #include #include #include #include #endif #include #include #include #include "sha1.h" #include "httpserverconnection.h" #include "http_common.h" #include "webservertask.h" HttpServerConnection:: HttpServerConnection(const std::string &label, WebServerTask *task, int fd, const char *ip, uint16_t port) : HttpConnection(label, task, fd, ip, port), owner_task(task) { } void HttpServerConnection::setOwner(Task *new_owner) { owner_task = dynamic_cast(new_owner); if (!owner_task) throw std::logic_error("expected WebServerTask"); Socket::setOwner(new_owner); } PollState HttpServerConnection::got_post_data(const char *buf, size_t len) { //log() << "got_post_data " << len << " bytes, " << remaining_post_data // << " left\n" << std::string(buf, len); size_t length = std::min(remaining_post_data, len); remaining_post_data -= length; if (remaining_post_data) { if (!buffer_post_data) if (!owner_task->partialPostData(this, buf, length)) return PollState::CLOSE; } else { if (buffer_post_data) state = owner_task->lastPostData(this, buffer.c_str(), buffer.size()); else state = owner_task->lastPostData(this, buf, len); if (len > length) { err_log() << "too much post data sent"; return PollState::CLOSE; } if (state == HttpState::SENDING_RESPONSE) return PollState::WRITE; else if (state == HttpState::CLOSE) return PollState::CLOSE; } return PollState::READ; } #ifdef USE_WEBROOT bool HttpServerConnection::prepareFileResponse(const std::string &url) { if (url.find("../") != std::string::npos) return false; std::string webroot = owner_task->webRoot(); if (webroot.empty()) return false; static_file_fd = open( (webroot + url).c_str(), O_RDONLY ); if (static_file_fd < 0) return false; struct stat sbuf; if (fstat(static_file_fd, &sbuf) < 0 || !S_ISREG(sbuf.st_mode)) { close(static_file_fd); return false; } remaining_bytes = static_cast(sbuf.st_size); sending_static_file = true; return true; } HttpState HttpServerConnection::sendHttpFileResponse(const std::string &hdrs, const std::string &mime) { if (!sending_static_file || state != HttpState::WAITING_FOR_REQUEST) { err_log() << "internal error, prepareFileResponse not called"; return HttpState::CLOSE; } sendHttpResponseHeader(hdrs, mime, remaining_bytes); if (remaining_bytes) remaining_bytes -= sendFileData(static_file_fd, remaining_bytes); if (remaining_bytes) return HttpState::SENDING_RESPONSE; else return HttpState::WAITING_FOR_REQUEST; } #endif PollState HttpServerConnection::writeData() { if (is_websocket) return wsWriteData(); if (state == HttpState::SENDING_RESPONSE) { // TODO: don't give conn to the owner, too much that can go wrong. size_t sent; #ifdef USE_WEBROOT if (sending_static_file) sent = sendFileData(static_file_fd, remaining_bytes); else #endif sent = owner_task->sendResponseData(this, remaining_bytes); remaining_bytes -= sent; if (remaining_bytes) return PollState::WRITE; #ifdef USE_WEBROOT if (sending_static_file) { close(static_file_fd); sending_static_file = false; } #endif state = HttpState::WAITING_FOR_REQUEST; return PollState::READ; } err_log() << "HttpServerConnection::writeData() called"; return PollState::CLOSE; } PollState HttpServerConnection::readData(char *buf, size_t len) { if (is_websocket) return wsReadData(buf, len); if (state == HttpState::READING_POST_DATA && !buffer_post_data) return got_post_data(buf, len); buffer.append(buf, len); return check_buffer(); } PollState HttpServerConnection::check_buffer() { if (state == HttpState::WAITING_FOR_REQUEST) { size_t end_of_request = buffer.find("\r\n\r\n"); if (end_of_request == std::string::npos) { if (buffer.size() > 20000) { log() << "error: no end of request"; return PollState::CLOSE; } return PollState::READ; } //log() << "Got request: " << buffer; current_request = buffer.substr(0, end_of_request+strlen("\r\n\r\n")); buffer.erase(0, current_request.size()); if (!parse_request()) { err_log() << "bad request"; return PollState::CLOSE; } if (current_method == "GET") { auto p = http_request_headers.find("sec-websocket-key"); if (p == http_request_headers.end()) { state = owner_task->newGetRequest(this, current_uri); } else { if (!owner_task->newWsRequest(this, current_uri)) return PollState::CLOSE; state = HttpState::WEBSOCKET; is_websocket = true; send_ws_handshake(p->second); if (notify_after_ws_handshake) owner_task->wsHandshakeFinished(this, current_uri); } if (buffer.size()) { log() << "unexpected data after GET request"; return PollState::CLOSE; } } else if (current_method == "POST") { try { auto p = http_request_headers.find("content-length"); if (p == http_request_headers.end()) { err_log() << "cannot find Content-Length"; // Just assume a very large post: remaining_post_data = std::string::npos; } else { remaining_post_data = std::stoul(p->second); } } catch (std::exception &) { err_log() << "cannot parse Content-Length"; return PollState::CLOSE; } buffer_post_data = false; state = owner_task->newPostRequest(this, current_uri); } else if (current_method == "OPTIONS") { state = owner_task->preflightRequest(this, current_uri); } else { return PollState::CLOSE; } } if (state == HttpState::CLOSE) return PollState::CLOSE; if (state == HttpState::SENDING_RESPONSE) return PollState::WRITE; if (buffer.empty()) return PollState::READ; if (state == HttpState::READING_POST_DATA) { PollState ret = got_post_data(buffer.c_str(), buffer.size()); if (!buffer_post_data || !remaining_post_data) buffer.clear(); return ret; } return check_buffer(); } namespace { inline int hexval(std::string::value_type c) { if (c >= '0' && c <= '9') return c-'0'; else if (c >= 'A' && c <= 'F') return (c - 'A' + 10); else if (c >= 'a' && c <= 'f') return (c - 'a' + 10); else return -1000; } } void HttpServerConnection::get_all_parameters(const char *qpos, std::multimap &pars) { const char *ppos = qpos; // Start of parameter name while (*qpos) { if (*qpos == '&') { // Epmty value: pars.insert(std::make_pair(std::string(ppos, qpos), std::string())); ++qpos; ppos = qpos; } else if (*qpos == '=') { // Urldecode value: std::string par_name = std::string(ppos, qpos); ++qpos; // Start of value std::ostringstream res; while (char c = *qpos) { if (c == '&') { ++qpos; break; } else if (c == '+') { ++qpos; c = ' '; } else if (c == '%') { // Should be two hex digits after %, // but if not, ignore errors and just keep the % if (*++qpos) { char c1 = *qpos, c2; if (*++qpos) { c2 = *qpos; if (c2) { ++qpos; int n = hexval(c1)*16+hexval(c2); if (n >= 0) c = static_cast(n); } } } } else { ++qpos; } res << c; } pars.insert(std::make_pair(par_name, res.str())); ppos = qpos; } else { ++qpos; } } } bool HttpServerConnection::parse_request() { std::string first_line; http_request_headers.clear(); if (!HttpCommon::parseHeaders(current_request, first_line, http_request_headers)) return false; size_t eom = first_line.find(' '); if (eom == std::string::npos) return false; size_t eor = first_line.find(' ', eom+1); if (eor == std::string::npos) return false; current_method = first_line.substr(0, eom); current_uri = first_line.substr(eom+1, eor-eom-1); { // HTTP/1.1 unsigned int major, minor; int pos = sscanf(first_line.substr(eor+1).c_str(), "HTTP/%u.%u", &major, &minor); if (pos != 2) return false; set_http_version(major, minor); } current_query_pars.clear(); size_t qpos = current_uri.find("?"); if (qpos != std::string::npos) { current_query_string = current_uri.substr(qpos+1); current_uri.resize(qpos); get_all_parameters(current_query_string.c_str(), current_query_pars); } else { current_query_string.clear(); } // uri might be given as http://some.domain/blah if (current_uri.substr(0, 4) == "http") { if (current_uri.substr(0, 7) == "http://") qpos = current_uri.find('/', 7); else if (current_uri.substr(0, 8) == "https://") qpos = current_uri.find('/', 8); else return true; if (qpos == std::string::npos) current_uri = "/"; else current_uri.erase(0, qpos); } return true; } size_t HttpServerConnection:: sendHttpResponse(const std::string &headers, const std::string &mime, const std::string &contents) { std::ostringstream response; response << headers << "Content-Length: " << contents.size() << "\r\nContent-Type: " << mime << "\r\n\r\n" << contents; size_t len = response.str().size(); asyncSendData(response.str()); return len; } size_t HttpServerConnection:: sendHttpResponseHeader(const std::string &headers, const std::string &mime, size_t content_length) { std::ostringstream response; response << headers << "Content-Length: " << content_length << "\r\nContent-Type: " << mime << "\r\n\r\n"; size_t len = response.str().size(); asyncSendData(response.str()); remaining_bytes = content_length; return len; } size_t HttpServerConnection:: sendChunkedResponseHeader(const std::string &headers, const std::string &mime) { sending_chunked_response = true; std::ostringstream response; response << headers << "Transfer-Encoding: chunked" << "\r\nContent-Type: " << mime << "\r\n\r\n"; size_t len = response.str().size(); asyncSendData(response.str()); return len; } size_t HttpServerConnection::sendChunk(const std::string &content) { if (!sending_chunked_response) { err_log("response encoding is not chunked"); return 0; } std::ostringstream chunk; chunk << std::hex << content.size() << "\r\n" << content << "\r\n"; asyncSendData(chunk.str()); return chunk.str().size(); } size_t HttpServerConnection::chunkedResponseComplete() { if (!sending_chunked_response) { err_log("response encoding is not chunked"); return 0; } std::string last_chunk = "0\r\n\r\n"; sending_chunked_response = false; asyncSendData(last_chunk); return last_chunk.size(); } bool HttpServerConnection::hasQueryPar(const std::string &name) const { return (current_query_pars.find(name) != current_query_pars.end()); } std::string HttpServerConnection::getQueryVal(const std::string &name) const { auto p = current_query_pars.find(name); if (p != current_query_pars.end()) return p->second; return std::string(); } std::string HttpServerConnection::getHeaderVal(const std::string &name) const { auto p = http_request_headers.find(name); if (p != http_request_headers.end()) return p->second; return std::string(); } std::map HttpServerConnection::currentRequestCookies() const { std::map c; auto r = getHeaderVals("cookie"); for (auto p=r.first; p!=r.second; ++p) { std::string s = p->second; while (!s.empty()) { auto pos1 = s.find('='); if (pos1 == std::string::npos) break; auto pos2 = s.find(';'); c[s.substr(0, pos1)] = s.substr(pos1+1, pos2-pos1-1); if (pos2 == std::string::npos) break; s.erase(0, s.find_first_not_of(" \t\r", pos2+1)); } } return c; } std::string HttpServerConnection::cookieVal(const std::string &name) const { if (name.find_first_of(" =;,\t") != std::string::npos) return ""; auto r = getHeaderVals("cookie"); for (auto p=r.first; p!=r.second; ++p) { auto pos = (" " + p->second).find(" " + name + "="); if (pos != std::string::npos) { auto first = p->second.find('=', pos); auto last = p->second.find(';', pos); if (last > first) { return p->second.substr(first+1, last-first-1); } } } return ""; } HttpServerConnection::~HttpServerConnection() { dbg_log() << "client connection closed"; #ifdef USE_WEBROOT if (sending_static_file) close(static_file_fd); #endif }