#include "microhttpd_server_p.h" #include "piliterals_string.h" #include "piliterals_time.h" #include // clang-format off #ifdef QNX # include # include # include # ifdef BLACKBERRY # include # else # include # endif #else # ifdef WINDOWS # include # include # include # else # include # include # ifdef LWIP # include # endif # endif #endif // clang-format on struct MicrohttpdServerConnection { bool ready(); int send_reply(const MicrohttpdServer::Reply & r); int send_error(); bool done = false; MicrohttpdServer::Method method = MicrohttpdServer::Method::Unknown; PIString path; PIByteArray body; PIMap headers, args, post; MHD_Connection * connection = nullptr; MicrohttpdServer * server = nullptr; MHD_PostProcessor * postprocessor = nullptr; }; bool MicrohttpdServerConnection::ready() { if (!server) return false; if (done) return true; done = true; MicrohttpdServer::Reply rep; if (method == MicrohttpdServer::Method::Get) { if (path == "/favicon.ico"_a) { // piCout << "send favicon" << server->favicon.size() << "bytes"; rep.setBody(server->favicon); send_reply(rep); return true; } } // piCout << "ready" << (int)method << path; MicrohttpdServer::Request req; req.method = method; req.path = path; req.body = body; req.headers = headers; req.args = args; rep.setCode(MHD_HTTP_BAD_REQUEST); if (server->callback) rep = server->callback(req); rep.addFixedHeaders(); send_reply(rep); return true; } int MicrohttpdServerConnection::send_reply(const MicrohttpdServer::Reply & r) { MHD_Response * response = MHD_create_response_from_buffer(r.body.size(), (void *)r.body.data(), MHD_RESPMEM_MUST_COPY); if (!response) { // piCout << "null response" << r.body.size() << (void *)r.body.data(); return MHD_NO; } auto it = r.headers.makeIterator(); while (it.next()) MHD_add_response_header(response, it.key().dataAscii(), it.value().dataUTF8()); // piCout << "status" << r.code; int ret = MHD_queue_response(connection, r.code, response); MHD_destroy_response(response); return ret; } int MicrohttpdServerConnection::send_error() { return MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY)); } void log_callback(void * cls, const char * fmt, va_list ap) { MicrohttpdServer * server = (MicrohttpdServer *)cls; piCout << "log" << server; if (!server) return; char buffer[1024]; memset(buffer, 0, 1024); std::vsnprintf(buffer, 1024, fmt, ap); piCout << buffer; } int iterate_post(void * conn_cls, MHD_ValueKind kind, const char * key, const char * filename, const char * content_type, const char * transfer_encoding, const char * data, uint64_t off, size_t size) { MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)conn_cls; if (!conn) return MHD_NO; conn->post[PIString::fromUTF8(key)] = PIString::fromUTF8(data); return MHD_YES; } void request_completed(void * cls, MHD_Connection * connection, void ** con_cls, MHD_RequestTerminationCode toe) { MicrohttpdServerConnection *& conn((MicrohttpdServerConnection *&)(*con_cls)); // piCout << "request_completed" << conn << conn->headers << conn->post << '"' << conn->body << '"'; if (!conn) return; if (conn->method == MicrohttpdServer::Method::Post && conn->postprocessor) MHD_destroy_post_processor(conn->postprocessor); conn->ready(); piDeleteSafety(conn); } int header_iterate(void * cls, MHD_ValueKind kind, const char * key, const char * value) { MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)cls; if (!conn) return MHD_NO; conn->headers[PIString::fromUTF8(key)] = PIString::fromUTF8(value); return MHD_YES; } int args_iterate(void * cls, MHD_ValueKind kind, const char * key, const char * value) { MicrohttpdServerConnection * conn = (MicrohttpdServerConnection *)cls; if (!conn) return MHD_NO; conn->args[PIString::fromUTF8(key)] = PIString::fromUTF8(value); return MHD_YES; } int answer_to_connection(void * cls, MHD_Connection * connection, const char * url, const char * method, const char * version, const char * upload_data, size_t * upload_data_size, void ** con_cls) { MicrohttpdServer * server = (MicrohttpdServer *)cls; MicrohttpdServer::Method m = MicrohttpdServer::Method::Unknown; if (0 == strcmp(method, "GET")) m = MicrohttpdServer::Method::Get; else if (0 == strcmp(method, "POST")) m = MicrohttpdServer::Method::Post; else if (0 == strcmp(method, "HEAD")) m = MicrohttpdServer::Method::Head; else if (0 == strcmp(method, "PUT")) m = MicrohttpdServer::Method::Put; else if (0 == strcmp(method, "DELETE")) m = MicrohttpdServer::Method::Delete; else if (0 == strcmp(method, "CONNECT")) m = MicrohttpdServer::Method::Connect; else if (0 == strcmp(method, "OPTIONS")) m = MicrohttpdServer::Method::Options; else if (0 == strcmp(method, "TRACE")) m = MicrohttpdServer::Method::Trace; else if (0 == strcmp(method, "PATCH")) m = MicrohttpdServer::Method::Patch; if (m == MicrohttpdServer::Method::Unknown) { piCout << "[MicrohttpdServer]" << "Warning:" << "Unknown method!"; return MHD_NO; } // piCout << "answer" << url << method << server; MicrohttpdServerConnection *& conn((MicrohttpdServerConnection *&)(*con_cls)); if (!conn) { conn = new MicrohttpdServerConnection(); conn->connection = connection; conn->server = server; conn->path = PIString::fromUTF8(url); conn->method = m; MHD_get_connection_values(connection, MHD_HEADER_KIND, (MHD_KeyValueIterator)header_iterate, *con_cls); MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, (MHD_KeyValueIterator)args_iterate, *con_cls); if (m == MicrohttpdServer::Method::Post) { // qDebug() << "new POST" << *upload_data_size; conn->postprocessor = MHD_create_post_processor(connection, 65536, (MHD_PostDataIterator)iterate_post, (void *)conn); } return MHD_YES; } switch (m) { case MicrohttpdServer::Method::Get: if (!conn->ready()) return conn->send_error(); return MHD_YES; case MicrohttpdServer::Method::Post: // qDebug() << "add POST" << *upload_data_size << PIString::fromUtf8(upload_data, *upload_data_size); if (*upload_data_size) { conn->body.append(upload_data, *upload_data_size); if (conn->postprocessor) MHD_post_process(conn->postprocessor, upload_data, *upload_data_size); *upload_data_size = 0; return MHD_YES; } else { // qDebug() << "answer ok"; if (!conn->ready()) return conn->send_error(); return MHD_YES; } break; default: break; } return conn->send_error(); } PRIVATE_DEFINITION_START(MicrohttpdServer) MHD_Daemon * daemon; PRIVATE_DEFINITION_END(MicrohttpdServer) MicrohttpdServer::MicrohttpdServer() { PRIVATE->daemon = nullptr; opts[Option::ConnectionLimit] = FD_SETSIZE - 4; opts[Option::ConnectionTimeout] = 0_s; } MicrohttpdServer::~MicrohttpdServer() { stop(); } void MicrohttpdServer::setOption(Option o, PIVariant v) { opts[o] = std::move(v); } void MicrohttpdServer::setFavicon(const PIByteArray & im) { favicon = im; } bool MicrohttpdServer::listen(PINetworkAddress addr) { stop(); uint flags = 0; #if MHD_VERSION <= 0x00095100 flags |= MHD_USE_POLL_INTERNALLY; #else flags |= MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD; #endif if (opts.value(Option::HTTPSEnabled).toBool()) flags |= MHD_USE_TLS; mem_key = opts.value(Option::HTTPSMemKey).toByteArray(); if (mem_key.isNotEmpty()) mem_key.append(0); mem_cert = opts.value(Option::HTTPSMemCert).toByteArray(); if (mem_cert.isNotEmpty()) mem_cert.append(0); key_pass = opts.value(Option::HTTPSKeyPassword).toByteArray(); if (key_pass.isNotEmpty()) key_pass.append(0); sockaddr_in sa_addr; memset(&sa_addr, 0, sizeof(sa_addr)); sa_addr.sin_port = htons(addr.port()); sa_addr.sin_addr.s_addr = addr.ip(); sa_addr.sin_family = AF_INET; PIVector options; options.append({MHD_OPTION_EXTERNAL_LOGGER, (intptr_t)log_callback, this}); options.append({MHD_OPTION_NOTIFY_COMPLETED, (intptr_t)request_completed, nullptr}); options.append({MHD_OPTION_CONNECTION_LIMIT, opts.value(Option::ConnectionLimit).toInt(), nullptr}); options.append({MHD_OPTION_CONNECTION_TIMEOUT, piRound(opts.value(Option::ConnectionTimeout).toSystemTime().toSeconds()), nullptr}); options.append({MHD_OPTION_SOCK_ADDR, 0, &sa_addr}); if (opts.value(Option::HTTPSEnabled).toBool()) { options.append({MHD_OPTION_HTTPS_MEM_KEY, 0, mem_key.data()}); options.append({MHD_OPTION_HTTPS_MEM_CERT, 0, mem_cert.data()}); options.append({MHD_OPTION_HTTPS_KEY_PASSWORD, 0, key_pass.data()}); } options.append({MHD_OPTION_END, 0, nullptr}); PRIVATE->daemon = MHD_start_daemon(flags, addr.port(), nullptr, nullptr, (MHD_AccessHandlerCallback)answer_to_connection, this, MHD_OPTION_ARRAY, options.data(), MHD_OPTION_END); return isListen(); } bool MicrohttpdServer::isListen() const { return PRIVATE->daemon; } void MicrohttpdServer::stop() { if (PRIVATE->daemon) { MHD_stop_daemon(PRIVATE->daemon); PRIVATE->daemon = nullptr; } } void MicrohttpdServer::Reply::addHeader(const PIString & header, const PIString & value) { headers[header] = value; } void MicrohttpdServer::Reply::removeHeader(const PIString & header) { headers.remove(header); } void MicrohttpdServer::Reply::setBody(const PIByteArray & b) { body = b; } void MicrohttpdServer::Reply::setCode(int c) { code = c; } void MicrohttpdServer::Reply::addFixedHeaders() { if (!headers.contains(MHD_HTTP_HEADER_CONTENT_TYPE)) { if (body.isNotEmpty()) { if (body.startsWith(PIByteArray::fromAscii(""))) addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "text/html; charset=utf-8"); else if (body[0] == '[' || body[0] == '{') addHeader(MHD_HTTP_HEADER_CONTENT_TYPE, "application/json; charset=utf-8"); } } addHeader(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*"); }