├── README.md ├── Source ├── Version.hpp ├── Marisa.hpp ├── App │ ├── Middlewares │ │ ├── Simple.cpp │ │ ├── Redirection.cpp │ │ ├── Websocket.cpp │ │ ├── Middlewares.hpp │ │ ├── Dashboard.cpp │ │ └── StaticFiles.cpp │ ├── Route.cpp │ ├── Route.hpp │ ├── Buffer.hpp │ ├── Context.hpp │ ├── Context.cpp │ ├── App.hpp │ ├── Response.hpp │ ├── Response.cpp │ ├── Request.hpp │ ├── Request.cpp │ └── App.cpp ├── CommonIncludes.hpp └── Util │ ├── Util.hpp │ ├── Util.cpp │ ├── DefaultStatusPage.cpp │ ├── URLEncode.cpp │ └── MimeTypes.cpp ├── Examples ├── static_files.cpp ├── send_single_file.cpp ├── hello.cpp ├── hello_streamed.cpp ├── dashboard.cpp ├── async_suspend_resume.cpp ├── hello_ssl.cpp ├── streamed_upload_pic.cpp └── usage_demo.cpp ├── Tests ├── thebenchmarker_webframeworks_test.cpp └── coverage.cpp ├── .gitlab-ci.yml ├── LICENSE ├── Benchmark.md ├── CMake └── FindMHD.cmake └── CMakeLists.txt /README.md: -------------------------------------------------------------------------------- 1 | # Marisa 2 | 3 | A lightening fast, express-like C++ web server framework. 4 | 5 | # Note 6 | This project is archived. For the **honorable** history of this project, please visit the [master](https://github.com/ReimuNotMoe/Marisa/tree/master) branch. 7 | 8 | In the meantime, you're invited to use [Resonance](https://github.com/SudoMaker/Resonance) instead, which allows development of event-driven async web application servers in both C++ and JavaScript. 9 | 10 | -------------------------------------------------------------------------------- /Source/Version.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #define MARISA_VERSION "2.0.0" -------------------------------------------------------------------------------- /Source/Marisa.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "Version.hpp" 16 | #include "App/App.hpp" 17 | #include "App/Middlewares/Middlewares.hpp" 18 | #include "App/Request.hpp" 19 | #include "App/Response.hpp" 20 | -------------------------------------------------------------------------------- /Source/App/Middlewares/Simple.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Middlewares.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | std::function Middlewares::Simple(std::string str) { 18 | return [str_ = std::make_shared(std::move(str))](Request *request, Response *response, Context *context) { 19 | response->send_persistent(str_->data(), str_->size()); 20 | }; 21 | } -------------------------------------------------------------------------------- /Examples/static_files.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | 14 | #include 15 | 16 | using namespace Marisa; 17 | using namespace Middlewares; 18 | 19 | int main() { 20 | App myapp; 21 | 22 | myapp.route("/files/**").async().use(StaticFiles("/tmp", true)); 23 | myapp.route("*").async().use(Redirection("/files/", 302)); 24 | 25 | myapp.listen(8080); 26 | myapp.start(); 27 | 28 | while (1) { 29 | sleep(-1); 30 | } 31 | } -------------------------------------------------------------------------------- /Examples/send_single_file.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | 17 | int main() { 18 | App myapp; 19 | 20 | myapp.route("/").async().use([](auto *req, Response *rsp, auto *ctx){ 21 | rsp->header["Content-Type"] = "text/plain"; 22 | 23 | rsp->send_file("/etc/fstab"); 24 | }); 25 | 26 | myapp.listen(8080); 27 | myapp.start(); 28 | 29 | while (1) { 30 | sleep(-1); 31 | } 32 | } -------------------------------------------------------------------------------- /Source/App/Route.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Route.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | Route &Route::stream() { 18 | mode_streamed = true; 19 | return *this; 20 | } 21 | 22 | Route &Route::async() { 23 | mode_async = true; 24 | return *this; 25 | } 26 | 27 | Route &Route::use(std::function func) { 28 | middlewares.emplace_back(std::move(func)); 29 | return *this; 30 | } 31 | -------------------------------------------------------------------------------- /Source/App/Middlewares/Redirection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Middlewares.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | std::function Middlewares::Redirection(std::string location, int status_code) { 18 | return [ctx = std::make_shared>(std::move(location), status_code)](Request *request, Response *response, Context *context){ 19 | response->header["Location"] = ctx->first; 20 | response->status = ctx->second; 21 | response->end(); 22 | }; 23 | } -------------------------------------------------------------------------------- /Examples/hello.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | 17 | int main() { 18 | App myapp; 19 | 20 | myapp.route("/").async().use([](auto *req, auto *rsp, auto *ctx){ 21 | rsp->send("" 22 | "" 23 | "Hello Marisa!" 24 | "" 25 | "" 26 | "

Hello Marisa!

" 27 | "" 28 | ""); 29 | }); 30 | 31 | myapp.listen(8080); 32 | myapp.start(); 33 | 34 | while (1) { 35 | sleep(-1); 36 | } 37 | } -------------------------------------------------------------------------------- /Source/App/Middlewares/Websocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Middlewares.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | std::function Middlewares::Websocket() { 18 | return [](Request *request, Response *response, Context *context) { 19 | auto ws_ver = request->header("Sec-WebSocket-Version"); 20 | auto ws_key = request->header("Sec-WebSocket-Key"); 21 | 22 | if (request->method() != "GET" || ws_ver.empty() || ws_key.empty()) { 23 | response->status = 400; 24 | response->end(); 25 | } else { 26 | response->upgrade(); 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /Tests/thebenchmarker_webframeworks_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | 17 | int main() { 18 | App myapp; 19 | 20 | myapp.route("/").async().use([](auto *req, auto *rsp, Context *ctx){ 21 | rsp->send(""); 22 | }); 23 | 24 | myapp.route("/user/:id").async().use([](Request *req, auto *rsp, auto *ctx){ 25 | rsp->send(req->url_vars()["id"]); 26 | }); 27 | 28 | myapp.route("/user").async().use([](auto *req, Response *rsp, auto *ctx){ 29 | rsp->send(""); 30 | }); 31 | 32 | myapp.listen(3000); 33 | myapp.start(); 34 | 35 | while (1) { 36 | sleep(-1); 37 | } 38 | } -------------------------------------------------------------------------------- /Examples/hello_streamed.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | using namespace Middlewares; 17 | 18 | int main() { 19 | App myapp; 20 | 21 | myapp.route("/").stream().use([](auto *req, auto *rsp, auto *ctx){ 22 | rsp->write("" 23 | "" 24 | "Hello Marisa!" 25 | "" 26 | ""); 27 | 28 | rsp->write("

Hello Marisa!

" 29 | "" 30 | ""); 31 | 32 | rsp->end(); 33 | }); 34 | 35 | myapp.listen(8080); 36 | myapp.start(); 37 | 38 | while (1) { 39 | sleep(-1); 40 | } 41 | } -------------------------------------------------------------------------------- /Examples/dashboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2018-2019 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as 7 | published by the Free Software Foundation, either version 3 of the 8 | License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | using namespace Marisa; 22 | using namespace Marisa::Middlewares; 23 | 24 | int main() { 25 | App myapp; 26 | 27 | myapp.route("/").async().use(Dashboard()); 28 | 29 | myapp.listen(8080); 30 | myapp.start(); 31 | 32 | while (1) { 33 | sleep(-1); 34 | } 35 | } -------------------------------------------------------------------------------- /Examples/async_suspend_resume.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | 17 | int main() { 18 | App myapp; 19 | 20 | myapp.route("/").async().use([state = 0](auto *req, Response *rsp, Context *ctx) mutable { 21 | if (!state) { 22 | // [1] 23 | std::thread ([&, ctx]{ 24 | // [3] 25 | // Do some blocking stuff here 26 | sleep(3); 27 | state = 1; 28 | ctx->resume(); 29 | }).detach(); 30 | 31 | // [2] 32 | ctx->suspend(); 33 | puts("suspend!"); 34 | } else { 35 | // [4] 36 | rsp->send("I'm back!"); 37 | } 38 | }); 39 | 40 | myapp.listen(8080); 41 | myapp.start(); 42 | 43 | while (1) { 44 | sleep(-1); 45 | } 46 | } -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: ubuntu:20.04 2 | 3 | stages: 4 | - build 5 | - test 6 | 7 | build: 8 | stage: build 9 | before_script: 10 | - apt-get update 11 | - DEBIAN_FRONTEND=noninteractive apt-get -yq install git build-essential cmake libmicrohttpd-dev zlib1g-dev 12 | - git submodule sync --recursive 13 | - git submodule update --init --recursive 14 | artifacts: 15 | untracked: true 16 | script: 17 | - mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_COVERAGE=ON -DCMAKE_CXX_FLAGS='-O0 -g -pg -fprofile-arcs -ftest-coverage' -DCMAKE_EXE_LINKER_FLAGS=-pg -DCMAKE_SHARED_LINKER_FLAGS=-pg .. && make 18 | 19 | coverage: 20 | stage: test 21 | before_script: 22 | - apt-get update 23 | - DEBIAN_FRONTEND=noninteractive apt-get -yq install lcov curl libmicrohttpd-dev libssl-dev zlib1g-dev netcat-openbsd ssl-cert 24 | dependencies: 25 | - build 26 | artifacts: 27 | untracked: true 28 | script: 29 | - cd build 30 | - sh -c './CoverageTest' 31 | - lcov -d . -c --output-file app-raw.info 32 | - lcov --remove app-raw.info '/usr/include/*' '*/_deps/*' -o app.info 33 | - lcov -l app.info 34 | -------------------------------------------------------------------------------- /Source/App/Middlewares/Middlewares.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../Context.hpp" 16 | #include "../../Util/Util.hpp" 17 | #include "../../Version.hpp" 18 | 19 | 20 | namespace Marisa::Middlewares { 21 | 22 | extern std::function Dashboard(); 23 | extern std::function Redirection(std::string location, int status_code = 302); 24 | extern std::function Simple(std::string str); 25 | extern std::function StaticFiles(std::string base_path, bool list_files = false); 26 | extern std::function Websocket(); 27 | 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2021 Reimu NotMoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Examples/hello_ssl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | using namespace Marisa; 16 | 17 | int main() { 18 | App myapp; 19 | 20 | myapp.route("/").async().use([](auto *req, auto *rsp, auto *ctx){ 21 | rsp->send("" 22 | "" 23 | "Hello Marisa!" 24 | "" 25 | "" 26 | "

Hello Marisa!

" 27 | "" 28 | ""); 29 | }); 30 | 31 | myapp.listen(8443); 32 | 33 | // Install the `ssl-cert' package to get them 34 | myapp.set_https_cert_file("/etc/ssl/certs/ssl-cert-snakeoil.pem"); 35 | myapp.set_https_key_file("/etc/ssl/private/ssl-cert-snakeoil.key"); 36 | 37 | myapp.start(); 38 | 39 | while (1) { 40 | sleep(-1); 41 | } 42 | } -------------------------------------------------------------------------------- /Source/App/Route.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | namespace Marisa { 18 | class Middleware; 19 | class Request; 20 | class Response; 21 | class Context; 22 | 23 | class Route { 24 | protected: 25 | std::optional> path_keys; 26 | std::vector> middlewares; 27 | bool mode_streamed = false; 28 | bool mode_async = false; 29 | 30 | public: 31 | Route() = default; 32 | 33 | Route &stream(); 34 | Route &async(); 35 | 36 | Route& use(std::function func); 37 | }; 38 | 39 | class RouteExposed : public Route { 40 | public: 41 | using Route::path_keys; 42 | using Route::middlewares; 43 | using Route::mode_streamed; 44 | using Route::mode_async; 45 | 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Source/App/Buffer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | // Buffer suitable for MHD 18 | 19 | namespace Marisa { 20 | class Buffer { 21 | private: 22 | void *memory_ = nullptr; 23 | size_t size_ = 0; 24 | public: 25 | Buffer() = default; 26 | 27 | void *memory() const noexcept { 28 | return memory_; 29 | } 30 | 31 | size_t size() const noexcept { 32 | return size_; 33 | } 34 | 35 | template 36 | void insert(const std::vector& buf) { 37 | insert(buf.data(), buf.size() * sizeof(T)); 38 | } 39 | 40 | template 41 | void insert(const T& buf) { 42 | insert(buf.data(), buf.size()); 43 | } 44 | 45 | void insert(const void *buf, size_t len) { 46 | if (!len) { 47 | return; 48 | } 49 | 50 | auto offset = size_; 51 | size_ += len; 52 | 53 | if (!memory_) { 54 | memory_ = malloc(size_); 55 | } else { 56 | memory_ = realloc(memory_, size_); 57 | } 58 | 59 | memcpy((uint8_t *)memory_+offset, buf, len); 60 | } 61 | 62 | }; 63 | 64 | } -------------------------------------------------------------------------------- /Source/CommonIncludes.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include 46 | 47 | #if MHD_VERSION < 0x00097001 48 | #define MHD_Result int 49 | #endif 50 | 51 | #include 52 | #include 53 | 54 | #include 55 | 56 | #include 57 | #include 58 | 59 | #include 60 | #include 61 | #include 62 | 63 | #include 64 | 65 | using namespace YukiWorkshop; 66 | using namespace SudoMaker; 67 | using namespace IODash; -------------------------------------------------------------------------------- /Source/Util/Util.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | namespace Marisa { 18 | namespace Util { 19 | class SpinLock { 20 | std::atomic_flag flag = ATOMIC_FLAG_INIT; 21 | public: 22 | void lock() { 23 | while (flag.test_and_set(std::memory_order_acquire)); 24 | } 25 | 26 | void unlock() { 27 | flag.clear(std::memory_order_release); 28 | } 29 | }; 30 | 31 | template 32 | static void explode_string(const std::string &s, char delim, T result) { 33 | std::stringstream ss(s); 34 | std::string item; 35 | while (std::getline(ss, item, delim)) { 36 | *(result++) = item; 37 | } 38 | } 39 | 40 | std::string read_file(const std::string& path); 41 | 42 | long cpus_available(); 43 | size_t memory_usage(); 44 | 45 | const std::string& mime_type(std::string dot_file_ext); 46 | 47 | const std::string& default_status_page(int status); 48 | 49 | std::string encodeURI(const std::string_view &__str); 50 | std::string encodeURIComponent(const std::string_view &__str); 51 | size_t decodeURI(const char *src, size_t src_size, char *dst); 52 | size_t decodeURIComponent(const char *src, size_t src_size, char *dst); 53 | std::string decodeURI(const std::string_view &__str); 54 | std::string decodeURIComponent(const std::string_view &__str); 55 | 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/App/Middlewares/Dashboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Middlewares.hpp" 14 | 15 | #include 16 | #include 17 | 18 | using namespace Marisa; 19 | 20 | std::function Middlewares::Dashboard() { 21 | return [](Request *request, Response *response, Context *context){ 22 | auto pid = getpid(); 23 | utsname uname_; 24 | uname(&uname_); 25 | rusage rusage_; 26 | getrusage(RUSAGE_SELF, &rusage_); 27 | 28 | auto page = fmt::format("\n" 29 | "\n" 30 | "Marisa Dashboard\n" 31 | "\n" 32 | "

Marisa Dashboard

\n" 33 | "
" 34 | "OS: {os}
" 35 | "PID: {pid}
" 36 | "Hardware concurrency: {cpus}
" 37 | "Memory usage: {rss:.3f} KiB
" 38 | "CPU time spent in userspace: {cputime_user:.6f} s
" 39 | "CPU time spent in kernel: {cputime_kern:.6f} s
" 40 | "Voluntary context switches: {nvcsw}
" 41 | "Involuntary context switches: {nivcsw}
" 42 | "
Marisa/" MARISA_VERSION "\n" 43 | "\n" 44 | "", 45 | fmt::arg("os", uname_.sysname), 46 | fmt::arg("pid", pid), 47 | fmt::arg("cpus", std::thread::hardware_concurrency()), 48 | fmt::arg("nvcsw", rusage_.ru_nvcsw), 49 | fmt::arg("nivcsw", rusage_.ru_nivcsw), 50 | fmt::arg("cputime_user", (double)rusage_.ru_utime.tv_sec + (double)rusage_.ru_utime.tv_usec / 1000000), 51 | fmt::arg("cputime_kern", rusage_.ru_stime.tv_sec + (double)rusage_.ru_stime.tv_usec / 1000000), 52 | fmt::arg("pid", pid), 53 | fmt::arg("rss", ((float)Util::memory_usage() / 1024) - (float)rusage_.ru_ixrss) 54 | ); 55 | 56 | response->send(page); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /Benchmark.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## Test 1: High end workstation 4 | - Test: [HelloAsync](https://github.com/ReimuNotMoe/Marisa/blob/master/Source/Tests/hello_async.cpp) 5 | - Configuration: 16 TCP instances (`app.listen(8080, 16)`) 6 | - Hardware: AMD Ryzen Threadripper 2950X **(not overclocked)**, 128GB DDR4 2400MHz 7 | - Environment: Linux kernel 5.0.5, GCC 7.4.0, libstdc++ 7.4.0, glibc 2.27 8 | - CFLAGS: `-O2 -march=znver1` 9 | - Benchmark tool: [wrk](https://github.com/wg/wrk) 10 | - Test command: `while :; do wrk -t 16 -c 384 http://127.0.0.1:8080; sleep 1; done` 11 | 12 | ### Results 13 | - Memory usage in benchmark: 6268 - 5188 = 1080 KB 14 | - Queries per second (QPS): ~220k 15 | - Screenshot: 16 | 17 | ![](https://raw.githubusercontent.com/ReimuNotMoe/ReimuNotMoe.github.io/master/images/marisa_benchmark_0.png) 18 | 19 | ## Test 2: Ancient laptop 20 | - Test: [HelloAsync](https://github.com/ReimuNotMoe/Marisa/blob/master/Source/Tests/hello_async.cpp) 21 | - Configuration: 2 TCP instances (`app.listen(8080, 2)`) 22 | - Server hardware: Intel Core 2 Duo U7700 @ 1.33 GHz, 2GB DDR2 667MHz 23 | - Server network adapter: Broadcom BCM5752 @ 1 Gbps 24 | - Environment: Ubuntu 18.10 stock 25 | - CFLAGS: `-O2 -march=core2` 26 | - Benchmark tool: [wrk](https://github.com/wg/wrk) 27 | - Test command: `while :; do wrk -t 8 -c 256 http://169.254.9.2:8080; sleep 1; done` 28 | 29 | ### Results 30 | - Memory usage in benchmark: 5824 - 4744 = 1080 KB 31 | - Queries per second (QPS): ~6.8k 32 | - Photo: 33 | 34 | ![](https://raw.githubusercontent.com/ReimuNotMoe/ReimuNotMoe.github.io/master/images/marisa_benchmark_1.jpg) 35 | 36 | ## Test 3: Awesome Gaming PC 37 | - Test: [HelloAsync](https://github.com/ReimuNotMoe/Marisa/blob/master/Source/Tests/hello_async.cpp) 38 | - Configuration: 16 TCP instances (`app.listen(8080, 16)`) 39 | - Hardware: Intel i9 9900K **(not overclocked)**, 64GB DDR4 3000MHz 40 | - Environment: Ubuntu 18.10 stock 41 | - CFLAGS: `-O2 -march=native` 42 | - Benchmark tool: [wrk](https://github.com/wg/wrk) 43 | - Test command: `wrk -c 256 -t 16 http://localhost:8080` 44 | 45 | ### Results 46 | - Memory usage in benchmark: 5932 - 5156 = 776 KB 47 | - Queries per second (QPS): ~255k 48 | - Photo: 49 | 50 | ![](https://raw.githubusercontent.com/ReimuNotMoe/ReimuNotMoe.github.io/master/images/marisa_benchmark_2.png) -------------------------------------------------------------------------------- /Examples/streamed_upload_pic.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | using namespace Marisa; 17 | using namespace Marisa::Middlewares; 18 | 19 | const char html_part1[] = "" 20 | "" 21 | "Streamed picture upload test" 22 | "" 23 | "" 24 | "

Streamed picture upload test

" 25 | "

" 26 | "
" 27 | "" 28 | "" 29 | "
"; 30 | const char html_part2[] = ""; 32 | const char html_part4[] = ""; 33 | 34 | int main() { 35 | App myapp; 36 | 37 | myapp.route("/").stream().use([](Request *req, Response *rsp, auto *ctx) { 38 | if (req->method() == "GET") { 39 | rsp->write(html_part1); 40 | rsp->write(html_part4); 41 | rsp->end(); 42 | } else if (req->method() == "POST") { 43 | std::string encoded; 44 | Base64X::Encoder b64_enc; 45 | 46 | while (req->read_post([&](auto &key, const std::string_view &value, auto &filename, auto &content_type, auto &transfer_encoding) { 47 | if (!value.empty()) { 48 | encoded += b64_enc.encode(value); 49 | // std::cout << "encoded size = " << encoded.size() << "\n"; 50 | } 51 | })); 52 | 53 | encoded += b64_enc.finalize(); 54 | std::cout << "done, encoded size = " << encoded.size() << "\n"; 55 | 56 | rsp->write(html_part1); 57 | rsp->write(html_part2); 58 | rsp->write(encoded); 59 | rsp->write(html_part3); 60 | rsp->write(html_part4); 61 | rsp->end(); 62 | } 63 | }); 64 | 65 | myapp.listen(8080); 66 | myapp.start(2); 67 | 68 | while (1) 69 | sleep(-1); 70 | } 71 | -------------------------------------------------------------------------------- /Source/Util/Util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Util.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | // Credit: https://nadeausoftware.com/articles/2012/07/c_c_tip_how_get_process_resident_set_size_physical_memory_use 18 | 19 | #if defined(_WIN32) 20 | #include "windows.h" 21 | #include "psapi.h" 22 | #endif 23 | 24 | #if defined(__APPLE__) && defined(__MACH__) 25 | #include 26 | #endif 27 | 28 | size_t Util::memory_usage() { 29 | #if defined(_WIN32) 30 | /* Windows -------------------------------------------------- */ 31 | PROCESS_MEMORY_COUNTERS info; 32 | GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) ); 33 | return (size_t)info.WorkingSetSize; 34 | 35 | #elif defined(__APPLE__) && defined(__MACH__) 36 | /* OSX ------------------------------------------------------ */ 37 | struct mach_task_basic_info info; 38 | mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; 39 | if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, 40 | (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) 41 | return (size_t)0L; /* Can't access? */ 42 | return (size_t)info.resident_size; 43 | 44 | #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) 45 | /* Linux ---------------------------------------------------- */ 46 | long rss = 0L; 47 | FILE* fp = NULL; 48 | if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL ) 49 | return (size_t)0L; /* Can't open? */ 50 | if ( fscanf( fp, "%*s%ld", &rss ) != 1 ) 51 | { 52 | fclose( fp ); 53 | return (size_t)0L; /* Can't read? */ 54 | } 55 | fclose( fp ); 56 | return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE); 57 | 58 | #else 59 | /* AIX, BSD, Solaris, and Unknown OS ------------------------ */ 60 | return (size_t)0L; /* Unsupported. */ 61 | #endif 62 | } 63 | 64 | long Util::cpus_available() { 65 | long nprocs = 1; 66 | #ifdef _WIN32 67 | #ifndef _SC_NPROCESSORS_ONLN 68 | SYSTEM_INFO info; 69 | GetSystemInfo(&info); 70 | #define sysconf(a) info.dwNumberOfProcessors 71 | #define _SC_NPROCESSORS_ONLN 72 | #endif 73 | #endif 74 | 75 | #ifdef _SC_NPROCESSORS_ONLN 76 | nprocs = sysconf(_SC_NPROCESSORS_ONLN); 77 | #endif 78 | 79 | return nprocs; 80 | } 81 | 82 | std::string Util::read_file(const std::string &path) { 83 | std::ifstream f(path); 84 | f.seekg(0, std::ios::end); 85 | size_t size = f.tellg(); 86 | std::string buf(size, ' '); 87 | f.seekg(0); 88 | f.read(&buf[0], size); 89 | return buf; 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /CMake/FindMHD.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find libmicrohttpd 2 | # Once done this will define 3 | # 4 | # MICROHTTPD_FOUND - system has libmicrohttpd 5 | # MICROHTTPD_INCLUDE_DIRS - the libmicrohttpd include directory 6 | # MICROHTTPD_LIBRARIES - Link these to use libmicrohttpd 7 | # MICROHTTPD_DEFINITIONS - Compiler switches required for using libmicrohttpd 8 | # 9 | # Copyright (c) 2011 Wesley Moore 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | 17 | if (LIBMICROHTTPD_LIBRARIES AND LIBMICROHTTPD_INCLUDE_DIRS) 18 | # in cache already 19 | set(LIBMICROHTTPD_FOUND TRUE) 20 | else (LIBMICROHTTPD_LIBRARIES AND LIBMICROHTTPD_INCLUDE_DIRS) 21 | # use pkg-config to get the directories and then use these values 22 | # in the FIND_PATH() and FIND_LIBRARY() calls 23 | 24 | find_path(LIBMICROHTTPD_INCLUDE_DIR 25 | NAMES 26 | microhttpd.h 27 | PATHS 28 | /usr/local/include 29 | /usr/local/opt/include 30 | /usr/include 31 | /usr/pkg/include 32 | /opt/local/include 33 | /sw/include 34 | ) 35 | 36 | find_library(LIBMICROHTTPD_LIBRARY 37 | NAMES 38 | microhttpd 39 | PATHS 40 | /usr/local/lib 41 | /usr/local/opt/lib 42 | /usr/lib 43 | /usr/pkg/lib 44 | /opt/local/lib 45 | /sw/lib 46 | ) 47 | 48 | if (LIBMICROHTTPD_LIBRARY) 49 | set(LIBMICROHTTPD_FOUND TRUE) 50 | endif (LIBMICROHTTPD_LIBRARY) 51 | 52 | set(LIBMICROHTTPD_INCLUDE_DIRS 53 | ${LIBMICROHTTPD_INCLUDE_DIR} 54 | ) 55 | 56 | if (LIBMICROHTTPD_FOUND) 57 | set(LIBMICROHTTPD_LIBRARIES 58 | ${LIBMICROHTTPD_LIBRARIES} 59 | ${LIBMICROHTTPD_LIBRARY} 60 | ) 61 | endif (LIBMICROHTTPD_FOUND) 62 | 63 | if (LIBMICROHTTPD_INCLUDE_DIRS AND LIBMICROHTTPD_LIBRARIES) 64 | set(LIBMICROHTTPD_FOUND TRUE) 65 | endif (LIBMICROHTTPD_INCLUDE_DIRS AND LIBMICROHTTPD_LIBRARIES) 66 | 67 | if (LIBMICROHTTPD_FOUND) 68 | if (NOT LIBMICROHTTPD_FIND_QUIETLY) 69 | message(STATUS "Found libmicrohttpd: ${LIBMICROHTTPD_INCLUDE_DIR} ${LIBMICROHTTPD_LIBRARIES}") 70 | endif (NOT LIBMICROHTTPD_FIND_QUIETLY) 71 | else (LIBMICROHTTPD_FOUND) 72 | if (LIBMICROHTTPD_FIND_REQUIRED) 73 | message(FATAL_ERROR "Could not find libmicrohttpd") 74 | endif (LIBMICROHTTPD_FIND_REQUIRED) 75 | endif (LIBMICROHTTPD_FOUND) 76 | 77 | # show the LIBMICROHTTPD_INCLUDE_DIRS and LIBMICROHTTPD_LIBRARIES variables only in the advanced view 78 | mark_as_advanced(LIBMICROHTTPD_INCLUDE_DIRS LIBMICROHTTPD_LIBRARIES) 79 | 80 | endif (LIBMICROHTTPD_LIBRARIES AND LIBMICROHTTPD_INCLUDE_DIRS) 81 | 82 | -------------------------------------------------------------------------------- /Source/App/Context.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | #include "App.hpp" 18 | #include "Response.hpp" 19 | #include "Request.hpp" 20 | #include "../Util/Util.hpp" 21 | 22 | namespace Marisa { 23 | 24 | class App; 25 | 26 | class Route; 27 | class RouteExposed; 28 | class Middleware; 29 | 30 | class Response; 31 | 32 | class Request; 33 | class RequestExposed; 34 | 35 | class Context { 36 | protected: 37 | // Global contexts 38 | App *app = nullptr; 39 | RouteExposed *route = nullptr; 40 | struct MHD_Connection *mhd_conn = nullptr; 41 | 42 | // Connection states 43 | bool conn_suspended = false; 44 | std::mutex conn_state_lock; 45 | size_t processed_post_size = 0; 46 | bool streamed_response_done = false; 47 | 48 | // Middleware execution states 49 | bool app_started = false; 50 | bool process_halted = false; 51 | size_t current_middleware_index = 0; 52 | std::function current_middleware; 53 | std::future app_future; 54 | 55 | protected: 56 | void suspend_connection(); 57 | void resume_connection(); 58 | 59 | bool match_route(); 60 | 61 | void app_container() noexcept; 62 | 63 | void start_app(); 64 | void wait_app_terminate(); 65 | 66 | void process_request(); 67 | 68 | public: 69 | /** 70 | * Check if current route is streamed. 71 | * 72 | * @return true for streamed. 73 | * 74 | */ 75 | bool streamed() const noexcept; 76 | 77 | /** 78 | * Halt the processing of middleware chain. 79 | * 80 | * Has no effect on currently running middleware. 81 | * When resumed, current middleware will be executed again. 82 | * 83 | * 84 | */ 85 | void suspend(); 86 | 87 | /** 88 | * Resume the processing of middleware chain. 89 | * 90 | * Usually called from another thread. 91 | * When resumed, last executed middleware will be executed again. 92 | * 93 | * 94 | */ 95 | void resume(); 96 | 97 | public: 98 | explicit Context(App *__app, struct MHD_Connection *__mhd_conn, const char *__mhd_url, const char *__mhd_method, const char *__mhd_version); 99 | 100 | ~Context(); 101 | 102 | Request request; 103 | Response response; 104 | 105 | spdlog::logger *logger = nullptr; 106 | }; 107 | 108 | class ContextExposed : public Context { 109 | public: 110 | using Context::app; 111 | using Context::route; 112 | using Context::logger; 113 | using Context::mhd_conn; 114 | 115 | using Context::conn_suspended; 116 | using Context::conn_state_lock; 117 | using Context::processed_post_size; 118 | using Context::streamed_response_done; 119 | 120 | using Context::app_started; 121 | using Context::process_halted; 122 | using Context::current_middleware_index; 123 | using Context::current_middleware; 124 | using Context::app_future; 125 | 126 | using Context::suspend_connection; 127 | using Context::resume_connection; 128 | using Context::match_route; 129 | using Context::app_container; 130 | using Context::start_app; 131 | using Context::wait_app_terminate; 132 | using Context::process_request; 133 | }; 134 | 135 | 136 | } -------------------------------------------------------------------------------- /Source/Util/DefaultStatusPage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Util.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | static const char http_status_template[] = "\n" 18 | "\n" 19 | "{status}\n" 20 | "\n" 21 | "

{status}

\n" 22 | "
Marisa/0.2
\n" 23 | "\n" 24 | ""; 25 | 26 | static std::unordered_map status_code_strings = { 27 | {100, "Continue"}, 28 | {101, "Switching Protocols"}, 29 | {102, "Processing"}, 30 | {200, "OK"}, 31 | {201, "Created"}, 32 | {202, "Accepted"}, 33 | {203, "Non-authoritative Information"}, 34 | {204, "No Content"}, 35 | {205, "Reset Content"}, 36 | {206, "Partial Content"}, 37 | {207, "Multi-Status"}, 38 | {208, "Already Reported"}, 39 | {226, "IM Used"}, 40 | {300, "Multiple Choices"}, 41 | {301, "Moved Permanently"}, 42 | {302, "Found"}, 43 | {303, "See Other"}, 44 | {304, "Not Modified"}, 45 | {305, "Use Proxy"}, 46 | {307, "Temporary Redirect"}, 47 | {308, "Permanent Redirect"}, 48 | {400, "Bad Request"}, 49 | {401, "Unauthorized"}, 50 | {402, "Payment Required"}, 51 | {403, "Forbidden"}, 52 | {404, "Not Found"}, 53 | {405, "Method Not Allowed"}, 54 | {406, "Not Acceptable"}, 55 | {407, "Proxy Authentication Required"}, 56 | {408, "Request Timeout"}, 57 | {409, "Conflict"}, 58 | {410, "Gone"}, 59 | {411, "Length Required"}, 60 | {412, "Precondition Failed"}, 61 | {413, "Payload Too Large"}, 62 | {414, "Request-URI Too Long"}, 63 | {415, "Unsupported Media Type"}, 64 | {416, "Requested Range Not Satisfiable"}, 65 | {417, "Expectation Failed"}, 66 | {418, "I'm a teapot"}, 67 | {421, "Misdirected Request"}, 68 | {422, "Unprocessable Entity"}, 69 | {423, "Locked"}, 70 | {424, "Failed Dependency"}, 71 | {426, "Upgrade Required"}, 72 | {428, "Precondition Required"}, 73 | {429, "Too Many Requests"}, 74 | {431, "Request Header Fields Too Large"}, 75 | {444, "Connection Closed Without Response"}, 76 | {451, "Unavailable For Legal Reasons"}, 77 | {499, "Client Closed Request"}, 78 | {500, "Internal Server Error"}, 79 | {501, "Not Implemented"}, 80 | {502, "Bad Gateway"}, 81 | {503, "Service Unavailable"}, 82 | {504, "Gateway Timeout"}, 83 | {505, "HTTP Version Not Supported"}, 84 | {506, "Variant Also Negotiates"}, 85 | {507, "Insufficient Storage"}, 86 | {508, "Loop Detected"}, 87 | {510, "Not Extended"}, 88 | {511, "Network Authentication Required"}, 89 | {599, "Network Connect Timeout Error"}, 90 | }; 91 | 92 | static std::unordered_map cached_status_page; 93 | 94 | static std::mutex lock; 95 | 96 | const std::string& Util::default_status_page(int status) { 97 | std::lock_guard lg(lock); 98 | 99 | auto it = cached_status_page.find(status); 100 | 101 | if (it == cached_status_page.end()) { 102 | std::string sbuf = std::to_string(status) + " " + status_code_strings[status]; 103 | std::string page = fmt::format(http_status_template, fmt::arg("status", sbuf)); 104 | cached_status_page[status] = std::move(page); 105 | return cached_status_page[status]; 106 | } else { 107 | return it->second; 108 | } 109 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(marisa) 3 | ENABLE_TESTING() 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | set(CPM_DOWNLOAD_VERSION 0.27.5) 8 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | 10 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 11 | message(STATUS "Downloading CPM.cmake") 12 | file(DOWNLOAD https://github.com/TheLartians/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ${CPM_DOWNLOAD_LOCATION}) 13 | endif() 14 | 15 | include(${CPM_DOWNLOAD_LOCATION}) 16 | 17 | CPMAddPackage( 18 | NAME IODash 19 | GITHUB_REPOSITORY YukiWorkshop/IODash 20 | VERSION 0.1.7 21 | GIT_SHALLOW ON 22 | ) 23 | 24 | CPMAddPackage( 25 | NAME ReGlob 26 | GITHUB_REPOSITORY SudoMaker/ReGlob 27 | VERSION 0.0.5 28 | GIT_SHALLOW ON 29 | ) 30 | 31 | CPMAddPackage( 32 | NAME ThreadPool 33 | GITHUB_REPOSITORY progschj/ThreadPool 34 | VERSION 1.0.0 35 | GIT_TAG 9a42ec1329f259a5f4881a291db1dcb8f2ad9040 36 | GIT_SHALLOW ON 37 | ) 38 | 39 | CPMAddPackage( 40 | NAME fmt 41 | GITHUB_REPOSITORY fmtlib/fmt 42 | VERSION 7.1.3 43 | GIT_TAG 7bdf0628b1276379886c7f6dda2cef2b3b374f0b 44 | GIT_SHALLOW ON 45 | ) 46 | 47 | add_definitions(-DSPDLOG_FMT_EXTERNAL) 48 | include_directories(${fmt_SOURCE_DIR}/include) 49 | 50 | CPMAddPackage( 51 | NAME spdlog 52 | GITHUB_REPOSITORY gabime/spdlog 53 | VERSION 1.8.2 54 | GIT_SHALLOW ON 55 | ) 56 | 57 | CPMAddPackage( 58 | NAME cpp-base64X 59 | GITHUB_REPOSITORY YukiWorkshop/cpp-base64X 60 | VERSION 0.0.2 61 | GIT_SHALLOW ON 62 | ) 63 | 64 | include(CMake/FindMHD.cmake) 65 | 66 | file(GLOB_RECURSE SOURCE_FILES "Source/*.cpp" "Source/*.hpp") 67 | 68 | include_directories(${LIBMICROHTTPD_INCLUDE_DIR}) 69 | include_directories(${spdlog_SOURCE_DIR}/include) 70 | include_directories(${ThreadPool_SOURCE_DIR}) 71 | include_directories(${cpp-base64X_SOURCE_DIR}) 72 | 73 | add_library(marisa ${SOURCE_FILES}) 74 | target_include_directories(marisa INTERFACE Source/) 75 | target_link_libraries(marisa pthread fmt cpp-base64X IODash ReGlob ${LIBMICROHTTPD_LIBRARIES}) 76 | 77 | OPTION(BUILD_COVERAGE "Build coverage test" OFF) 78 | 79 | if (BUILD_COVERAGE) 80 | add_executable(CoverageTest Tests/coverage.cpp) 81 | target_link_libraries(CoverageTest marisa gcov) 82 | 83 | add_test(NAME Coverage COMMAND CoverageTest) 84 | endif (BUILD_COVERAGE) 85 | 86 | add_executable(test_thebenchmarker_webframeworks Tests/thebenchmarker_webframeworks_test.cpp) 87 | target_link_libraries(test_thebenchmarker_webframeworks marisa) 88 | 89 | add_executable(example_Hello Examples/hello.cpp) 90 | target_link_libraries(example_Hello marisa) 91 | 92 | add_executable(example_HelloStreamed Examples/hello_streamed.cpp) 93 | target_link_libraries(example_HelloStreamed marisa) 94 | 95 | add_executable(example_HelloSSL Examples/hello_ssl.cpp) 96 | target_link_libraries(example_HelloSSL marisa) 97 | 98 | add_executable(example_AsyncSuspendResume Examples/async_suspend_resume.cpp) 99 | target_link_libraries(example_AsyncSuspendResume marisa) 100 | 101 | add_executable(example_UsageDemo Examples/usage_demo.cpp) 102 | target_link_libraries(example_UsageDemo marisa) 103 | 104 | add_executable(example_Dashboard Examples/dashboard.cpp) 105 | target_link_libraries(example_Dashboard marisa) 106 | 107 | add_executable(example_SendSingleFile Examples/send_single_file.cpp) 108 | target_link_libraries(example_SendSingleFile marisa) 109 | 110 | add_executable(example_StaticFiles Examples/static_files.cpp) 111 | target_link_libraries(example_StaticFiles marisa) 112 | 113 | add_executable(example_StreamedPicUpload Examples/streamed_upload_pic.cpp) 114 | target_link_libraries(example_StreamedPicUpload marisa) 115 | -------------------------------------------------------------------------------- /Examples/usage_demo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | #include 16 | 17 | using namespace Marisa; 18 | using namespace Middlewares; 19 | using namespace Util; 20 | 21 | 22 | int main() { 23 | App myapp; 24 | 25 | auto func = [](Request *request, Response *response, Context *context){ 26 | response->measure_execution_time(); 27 | 28 | std::ostringstream ss; 29 | ss << "\n" 30 | "\n" 31 | << "Usage Demo\n" 32 | "" 66 | ""; 67 | 68 | ss << "" 69 | "

Hello world!

" 70 | "
" 71 | "URL: " << request->url() << "
" 72 | "Method: " << request->method() << "
" 73 | "Socket address: " << request->socket_address().to_string() << "

"; 74 | 75 | 76 | ss << "Headers:
" 77 | "" 78 | "" 79 | "" 80 | "" 81 | ""; 82 | 83 | 84 | for (auto &it : request->header()) { 85 | ss << ""; 86 | ss << ""; 87 | ss << ""; 88 | ss << ""; 89 | } 90 | 91 | 92 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 93 | 94 | ss << "

"; 95 | 96 | ss << "Cookies:
" 97 | "" 98 | "" 99 | "" 100 | "" 101 | ""; 102 | 103 | 104 | for (auto &it : request->cookie()) { 105 | ss << ""; 106 | ss << ""; 107 | ss << ""; 108 | ss << ""; 109 | } 110 | 111 | 112 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 113 | 114 | ss << "

"; 115 | 116 | ss << "URL Variables:
" 117 | << "" 118 | "" 119 | "" 120 | "" 121 | ""; 122 | 123 | 124 | for (auto &it : request->url_vars()) { 125 | ss << ""; 126 | ss << ""; 127 | ss << ""; 128 | ss << ""; 129 | } 130 | 131 | 132 | ss << "
Key/IndexValue
" << it.first << "" << it.second << "
"; 133 | 134 | ss << "

"; 135 | 136 | ss << "URL Query Strings:
" 137 | "" 138 | "" 139 | "" 140 | "" 141 | ""; 142 | 143 | 144 | for (auto &it : request->query()) { 145 | ss << ""; 146 | ss << ""; 147 | ss << ""; 148 | ss << ""; 149 | } 150 | 151 | 152 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 153 | 154 | ss << "

"; 155 | 156 | ss << "URI encode/decode tests:" << "
"; 157 | ss << ""; 158 | ss << "encodeURI(\"aa=bb&cc=dd大花猫+++\") = \"" << encodeURI("aa=bb&cc=dd大花猫+++") << "\"
"; 159 | ss << "encodeURIComponent(\"aa=bb&cc=dd大花猫+++\") = \"" << encodeURIComponent("aa=bb&cc=dd大花猫+++") << "\"
"; 160 | ss << R"(decodeURI("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") = ")" << decodeURI("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") << "\"
"; 161 | ss << R"(decodeURIComponent("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") = ")" << decodeURIComponent("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") << "\"
"; 162 | ss << R"(decodeURI("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") = ")" << decodeURI("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") << "\"
"; 163 | ss << R"(decodeURIComponent("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") = ")" << decodeURIComponent("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") << "\"
"; 164 | ss << "
"; 165 | 166 | // ss << "base64 encode/decode tests:" << "
"; 167 | // ss << ""; 168 | // ss << "Base64::Encoder::encode_once(\"The quick brown fox jumps over the lazy dog\") = \"" << Base64::Encoder::encode_once("The quick brown fox jumps over the lazy dog") << "\"
"; 169 | // ss << "Base64::Decoder::decode_once(\"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw\") = \"" << Base64::Decoder::decode_once("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw") << "\"
"; 170 | // ss << "
"; 171 | 172 | ss << "
Marisa/" << MARISA_VERSION << "\n"; 173 | ss << ""; 174 | 175 | response->send(ss.str()); 176 | }; 177 | 178 | myapp.route("/:foo/:bar/**").async().use(func); 179 | myapp.route("/**").async().use(func); 180 | 181 | myapp.listen(8080); 182 | myapp.start(); 183 | 184 | while (1) { 185 | sleep(-1); 186 | } 187 | } -------------------------------------------------------------------------------- /Source/App/Middlewares/StaticFiles.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Middlewares.hpp" 14 | 15 | #include 16 | #include 17 | 18 | static const char ModuleName[] = "StaticFiles"; 19 | 20 | using namespace Marisa; 21 | using namespace Marisa::Util; 22 | 23 | static void generate_file_page(const std::string& base_path, DIR *__dirp, Request *request, Response *response, Context *context) { 24 | errno = 0; 25 | 26 | std::stringstream ss; 27 | 28 | std::string title = "Index of " + request->url(); 29 | 30 | ss << "" 31 | "" 32 | "" 33 | "" 34 | << title 35 | << "" 36 | "" 75 | "" 76 | "" 77 | "

" 78 | << title 79 | << "

" 80 | "" 81 | "" 82 | "" 83 | "" 84 | "" 85 | "" 86 | ""; 87 | 88 | struct dirent *de = nullptr; 89 | struct stat sbuf; 90 | 91 | while ((de = readdir(__dirp))) { 92 | if (strcmp(de->d_name, ".") == 0) 93 | continue; 94 | 95 | auto full_path = base_path + "/" + de->d_name; 96 | stat(full_path.c_str(), &sbuf); 97 | 98 | ss << "" 99 | "" 111 | "" 120 | "" 132 | "" 135 | ""; 136 | 137 | } 138 | 139 | ss << "
TypeNameLast modifiedSize
"; 100 | 101 | if (de->d_type == DT_DIR) 102 | ss << "DIR"; 103 | else if (de->d_type == DT_REG) 104 | ss << "REG"; 105 | else if (de->d_type == DT_LNK) 106 | ss << "LNK"; 107 | else if (de->d_type == DT_SOCK) 108 | ss << "SOCK"; 109 | 110 | ss << "d_name); 114 | if (de->d_type == DT_DIR) 115 | ss << "/"; 116 | ss << "\">" << de->d_name; 117 | 118 | ss << 119 | ""; 121 | 122 | timespec mtim_; 123 | 124 | #ifdef __APPLE__ 125 | mtim_ = sbuf.st_mtimespec; 126 | #else 127 | mtim_ = sbuf.st_mtim; 128 | #endif 129 | 130 | ss << "TODO"; 131 | ss << ""; 133 | ss << sbuf.st_size; 134 | ss << "
" 140 | "
Marisa/" MARISA_VERSION "
" 141 | ""; 142 | 143 | response->send(ss.str()); 144 | } 145 | 146 | std::function Middlewares::StaticFiles(std::string base_path, bool list_files) { 147 | return [ctx = std::make_shared>(std::move(base_path), list_files)](Request *request, Response *response, Context *context){ 148 | auto &base_path = ctx->first; 149 | auto &list_files = ctx->second; 150 | 151 | auto it_path = request->url_vars().find("1"); 152 | 153 | if (it_path == request->url_vars().end()) { 154 | context->logger->error("[{} @ ?] Incorrectly configured route. Please use something like `/foo/bar/**'.", ModuleName); 155 | response->status = 500; 156 | response->send(default_status_page(response->status)); 157 | return; 158 | } 159 | 160 | auto path = decodeURIComponent(it_path->second); 161 | 162 | // Get rid of script kiddies 163 | if (path.find("/../") != std::string::npos) { 164 | response->status = 200; 165 | response->send("You are a teapot"); 166 | return; 167 | } 168 | 169 | auto full_path = base_path + "/" + path; 170 | 171 | context->logger->debug("[{} @ ?] full_path: {}", ModuleName, full_path); 172 | 173 | 174 | struct stat stat_buf; 175 | if (stat(full_path.c_str(), &stat_buf)) { 176 | if (errno == ENOENT) { 177 | response->status = 404; 178 | } else if (errno == EPERM) { 179 | response->status = 403; 180 | } else { 181 | response->status = 500; 182 | } 183 | 184 | response->send_status_page(); 185 | return; 186 | } 187 | 188 | DIR *dirp = nullptr; 189 | 190 | if ((dirp = opendir((full_path + "/").c_str()))) { // Directory 191 | if (list_files) { 192 | generate_file_page(base_path, dirp, request, response, context); 193 | } else { 194 | response->status = 403; 195 | response->send_status_page(); 196 | } 197 | 198 | closedir(dirp); 199 | } else { // File 200 | if (errno == ENOENT) { 201 | response->status = 404; 202 | } else if (errno == EPERM) { 203 | response->status = 403; 204 | } else if (errno == ENOTDIR) { 205 | try { 206 | auto dot_pos = path.find_last_of('.'); 207 | if (dot_pos == path.npos) 208 | response->header["Content-Type"] = "application/octet-stream"; 209 | else 210 | response->header["Content-Type"] = mime_type(path.substr(dot_pos + 1)); 211 | 212 | response->send_file(full_path); 213 | return; 214 | } catch (std::system_error& e) { 215 | context->logger->error("[{} @ ?] send_file error: {}", ModuleName, e.what()); 216 | response->status = 500; 217 | response->send_status_page(); 218 | return; 219 | } 220 | } else { 221 | response->status = 500; 222 | } 223 | response->send_status_page(); 224 | return; 225 | } 226 | }; 227 | } 228 | -------------------------------------------------------------------------------- /Source/App/Context.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Context.hpp" 14 | #include "App.hpp" 15 | #include "Response.hpp" 16 | #include "../Util/Util.hpp" 17 | 18 | using namespace Marisa; 19 | 20 | const char ModuleName[] = "Context"; 21 | 22 | Context::Context(App *__app, struct MHD_Connection *__mhd_conn, const char *__mhd_url, const char *__mhd_method, const char *__mhd_version) : 23 | app(__app), mhd_conn(__mhd_conn), request(this, __mhd_url, __mhd_method, __mhd_version), response(this) { 24 | logger = app->logger_internal.get(); 25 | match_route(); 26 | static_cast(request).init(); 27 | static_cast(response).init(); 28 | } 29 | 30 | bool Context::streamed() const noexcept { 31 | if (route) 32 | return route->mode_streamed; 33 | else 34 | return false; 35 | } 36 | 37 | void Context::process_request() { 38 | if (route) { 39 | if (route->middlewares.empty()) { 40 | logger->error("[{} @ {:x}] Route {:x}: No middlewares", ModuleName, (intptr_t)this, (intptr_t)route); 41 | response.send_status_page(500); 42 | return; 43 | } else { 44 | return; 45 | } 46 | } else { // Route not matched, just send error page on event loop and tear down 47 | logger->debug("[{} @ {:x}] Route not found", ModuleName, (intptr_t)this, (intptr_t)route); 48 | 49 | response.send_status_page(404); 50 | return; // Return and teardown 51 | } 52 | } 53 | 54 | void Context::start_app() { 55 | if (route->mode_async) { 56 | app_started = true; 57 | logger->debug("[{} @ {:x}] running app locally", ModuleName, (intptr_t) this); 58 | app_container(); 59 | } else { 60 | logger->debug("[{} @ {:x}] running app in thread pool", ModuleName, (intptr_t) this); 61 | 62 | app_future = app->app_thread_pool().enqueue([this] { 63 | app_container(); 64 | }); 65 | 66 | app_started = true; 67 | } 68 | } 69 | 70 | void Context::wait_app_terminate() { 71 | if (app_started && !route->mode_async) { 72 | app_future.get(); 73 | logger->debug("[{} @ {:x}] app work terminated", ModuleName, (intptr_t) this); 74 | } 75 | } 76 | 77 | void Context::app_container() noexcept { 78 | try { 79 | // Check for previously halted middleware 80 | if (current_middleware) { 81 | // Run it again 82 | logger->debug(R"([{} @ {:x}] middleware processing resumed)", ModuleName, (intptr_t)this); 83 | current_middleware(&request, &response, this); 84 | 85 | if (process_halted) { // It halted again 86 | suspend_connection(); 87 | app_started = false; 88 | logger->debug(R"([{} @ {:x}] middleware processing halted)", ModuleName, (intptr_t)this); 89 | return; 90 | } else { // It done processing 91 | current_middleware_index++; 92 | } 93 | } 94 | 95 | for (; current_middleware_index < route->middlewares.size(); current_middleware_index++) { 96 | current_middleware = route->middlewares[current_middleware_index]; 97 | 98 | logger->debug(R"([{} @ {:x}] calling middleware #{})", ModuleName, (intptr_t)this, current_middleware_index); 99 | 100 | current_middleware(&request, &response, this); 101 | 102 | if (process_halted) { 103 | suspend_connection(); 104 | app_started = false; 105 | logger->debug(R"([{} @ {:x}] middleware processing halted)", ModuleName, (intptr_t)this); 106 | return; 107 | } 108 | 109 | if (static_cast(response).finalized) { 110 | logger->debug(R"([{} @ {:x}] response finalized, not advancing middleware index)", ModuleName, (intptr_t)this); 111 | break; 112 | } 113 | } 114 | 115 | if (!static_cast(response).finalized) { 116 | logger->warn(R"([{} @ {:x}] response still not finalized after going through all middlewares)", ModuleName, (intptr_t)this); 117 | static_cast(response).end(); 118 | } 119 | } catch (std::exception &e) { 120 | logger->error("[{} @ {:x}] uncaught exception in middleware #{}: {}", ModuleName, (intptr_t) this, current_middleware_index, e.what()); 121 | } 122 | 123 | logger->debug("[{} @ {:x}] app_container terminated", ModuleName, (intptr_t) this); 124 | } 125 | 126 | bool Context::match_route() { 127 | // Match URL with routes 128 | logger->debug(R"([{} @ {:x}] match_route called)", ModuleName, (intptr_t)this); 129 | 130 | std::smatch url_smatch; // Don't let this hell go out of scope 131 | for (auto &it : app->routes()) { 132 | auto &rte = *std::static_pointer_cast(it.second); 133 | if (rte.path_keys) { 134 | request.url_vars() = ReGlob::PathMatch(it.first, rte.path_keys.value(), request.url()); 135 | if (!request.url_vars().empty()) { 136 | route = reinterpret_cast(it.second.get()); 137 | return true; 138 | } 139 | } else { 140 | if (std::regex_search(request.url(), url_smatch, it.first)) { 141 | size_t smpos = 0; 142 | for (auto &its : url_smatch) { 143 | request.url_vars()[std::to_string(smpos)] = std::move(its.str()); 144 | // request.url_matched_capture_groups().push_back(its.str()); 145 | smpos++; 146 | } 147 | route = reinterpret_cast(it.second.get()); 148 | logger->debug(R"([{} @ {:x}] route found, ptr={})", ModuleName, (intptr_t) this, (intptr_t) route); 149 | 150 | return true; 151 | } 152 | } 153 | } 154 | 155 | if (app->route_global()) { 156 | route = static_cast(app->route_global().get()); 157 | logger->debug(R"([{} @ {:x}] global route found, ptr={})", ModuleName, (intptr_t)this, (intptr_t)route); 158 | 159 | return true; 160 | } 161 | 162 | return false; 163 | } 164 | 165 | void Context::suspend_connection() { 166 | std::unique_lock lk(conn_state_lock); 167 | if (!conn_suspended) { 168 | MHD_suspend_connection(mhd_conn); 169 | 170 | conn_suspended = true; 171 | 172 | logger->debug(R"([{} @ {:x}] conn suspended)", ModuleName, (intptr_t) this); 173 | } else { 174 | logger->debug(R"([{} @ {:x}] conn already suspended)", ModuleName, (intptr_t) this); 175 | } 176 | } 177 | 178 | void Context::resume_connection() { 179 | std::unique_lock lk(conn_state_lock); 180 | 181 | if (conn_suspended) { 182 | MHD_resume_connection(mhd_conn); 183 | conn_suspended = false; 184 | 185 | logger->debug(R"([{} @ {:x}] conn resumed)", ModuleName, (intptr_t)this); 186 | } else { 187 | logger->debug(R"([{} @ {:x}] conn already resumed)", ModuleName, (intptr_t)this); 188 | } 189 | 190 | } 191 | 192 | void Context::suspend() { 193 | process_halted = true; 194 | logger->debug(R"([{} @ {:x}] user halted middleware processing)", ModuleName, (intptr_t)this); 195 | } 196 | 197 | void Context::resume() { 198 | process_halted = false; 199 | resume_connection(); 200 | logger->debug(R"([{} @ {:x}] user resumed middleware processing)", ModuleName, (intptr_t)this); 201 | } 202 | 203 | Context::~Context() { 204 | wait_app_terminate(); 205 | } 206 | 207 | 208 | -------------------------------------------------------------------------------- /Source/App/App.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | #include "../Util/Util.hpp" 17 | #include "../Version.hpp" 18 | #include "Context.hpp" 19 | #include "Route.hpp" 20 | 21 | using namespace IODash; 22 | 23 | namespace Marisa { 24 | class Route; 25 | 26 | class App { 27 | public: 28 | struct Config { 29 | struct logging { 30 | struct internal { 31 | spdlog::level::level_enum level = spdlog::level::debug; /*!< Loglevel of internal components */ 32 | std::string pattern = "%Y-%m-%d %T.%f %z [%^%l%$] %v"; /*!< Log pattern of internal components. See spdlog manual for details */ 33 | bool stdout_enabled = true; /*!< Log to standard output */ 34 | std::string file; /*!< File path to log to. Disabled if unset */ 35 | } internal; 36 | 37 | struct access { 38 | spdlog::level::level_enum level = spdlog::level::debug; /*!< Loglevel of access logs */ 39 | std::string pattern = "%Y-%m-%d %T.%f %z [%^%l%$] %v"; /*!< Log pattern of access logs. See spdlog manual for details */ 40 | bool stdout_enabled = true; /*!< Log to standard output */ 41 | std::string file; /*!< File path to log to. Disabled if unset */ 42 | } access; 43 | } logging; 44 | 45 | struct connection { 46 | uint16_t timeout_seconds = 60; /*!< Connection timeout in seconds */ 47 | size_t max_connections = 10485760; /*!< Max allowed connections */ 48 | size_t max_post_size = 128 * 1024; /*!< Max size of POST data allowed in normal mode */ 49 | } connection; 50 | 51 | struct app { 52 | size_t thread_pool_size_ratio = 6; /*!< Use x threads for every processor core */ 53 | } app; 54 | 55 | bool ignore_sigpipe = true; /*!< Ignores SIGPIPE */ 56 | } config; 57 | 58 | std::unique_ptr logger_internal; 59 | std::unique_ptr logger_access; 60 | protected: 61 | int mhd_flags = MHD_ALLOW_SUSPEND_RESUME | MHD_USE_AUTO_INTERNAL_THREAD | MHD_ALLOW_UPGRADE | MHD_USE_ERROR_LOG | MHD_USE_DEBUG; 62 | 63 | std::string https_cert, https_key, https_trust, https_key_passwd; 64 | 65 | struct MHD_Daemon *mhd_daemon = nullptr; 66 | SocketAddress listen_addr; 67 | 68 | std::unique_ptr app_thread_pool_; 69 | 70 | std::shared_ptr spdlog_internal_sink_stdout; 71 | std::shared_ptr spdlog_internal_sink_file; 72 | 73 | std::shared_ptr spdlog_access_sink_stdout; 74 | std::shared_ptr spdlog_access_sink_file; 75 | 76 | std::vector>> route_mapping; 77 | std::shared_ptr route_global_; 78 | 79 | protected: 80 | void init(); 81 | void init_logger(); 82 | protected: 83 | static MHD_Result mhd_connection_handler(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, 84 | size_t *upload_data_size, void **con_cls); 85 | static void mhd_request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe); 86 | 87 | static ssize_t mhd_streamed_response_reader(void *cls, uint64_t pos, char *buf, size_t max); 88 | static void mhd_streamed_response_read_done(void *cls); 89 | 90 | static void ignore_sigpipe(); 91 | public: 92 | App(); 93 | App(App::Config cfg); 94 | 95 | /** 96 | * Listen on specified port. 97 | * 98 | * @param port The port to listen on. 99 | * @param ipv6_enabled Enable IPv6 (dual stack). 100 | * 101 | */ 102 | void listen(uint16_t port, bool ipv6_enabled = 1); 103 | 104 | /** 105 | * Listen on specified IPv4 address. 106 | * 107 | * @param address Address in string form. e.g. "0.0.0.0" 108 | * @param port Port. e.g. 8080 109 | * 110 | */ 111 | void listen_v4(const std::string &address, uint16_t port); 112 | 113 | /** 114 | * Listen on specified IPv6 address. 115 | * 116 | * @param address Address in string form. e.g. "::" 117 | * @param port Port. e.g. 8080 118 | * 119 | */ 120 | void listen_v6(const std::string &address, uint16_t port); 121 | 122 | /** 123 | * Set HTTPS certificate (string form). 124 | * 125 | * @param str PEM certificate in string form. 126 | * 127 | */ 128 | void set_https_cert(const std::string& str); 129 | 130 | /** 131 | * Set HTTPS certificate (file). 132 | * 133 | * @param path Path to a PEM certificate. 134 | * 135 | */ 136 | void set_https_cert_file(const std::string& path); 137 | 138 | /** 139 | * Set HTTPS private key (string form). 140 | * 141 | * @param str Private key in string form. 142 | * 143 | */ 144 | void set_https_key(const std::string& str); 145 | 146 | /** 147 | * Set HTTPS private key (file). 148 | * 149 | * @param path Path to a private key. 150 | * 151 | */ 152 | void set_https_key_file(const std::string& path); 153 | 154 | /** 155 | * Set password of the HTTPS private key. 156 | * 157 | * @param str Password of the HTTPS private key. 158 | * 159 | */ 160 | void set_https_key_passwd(const std::string& str); 161 | 162 | 163 | void set_https_trust(const std::string& str); 164 | void set_https_trust_file(const std::string& path); 165 | 166 | /** 167 | * Add a route denoted by a glob or path expression. 168 | * 169 | * e.g. "/file/*", "/user/:id". 170 | * You can access the variables from request->url_vars() . 171 | * For the global route, use a single asterisk ("*"). 172 | * 173 | * @param expression Route expression. 174 | * \return Created route object. 175 | * 176 | */ 177 | Route &route(const std::string &expression); 178 | 179 | /** 180 | * Add a route denoted by a regular expression object. 181 | * 182 | * You can access the captured strings from requests->url_vars() . 183 | * 184 | * @param regexp Regular expression object. 185 | * \return Created route object. 186 | * 187 | */ 188 | Route &route(std::regex regexp); 189 | 190 | /** 191 | * Start the server. 192 | * 193 | * @param io_thread_count Threads used for network I/O handling. -1 for auto detect. 194 | * 195 | */ 196 | void start(ssize_t io_thread_count = -1); 197 | 198 | /** 199 | * Stop the server. 200 | * 201 | */ 202 | void stop(); 203 | 204 | /** 205 | * Registered routes on this App. 206 | * 207 | */ 208 | std::vector>>& routes() noexcept { 209 | return route_mapping; 210 | } 211 | 212 | /** 213 | * Global route on this App. 214 | * 215 | */ 216 | std::shared_ptr& route_global() noexcept { 217 | return route_global_; 218 | } 219 | 220 | /** 221 | * Thread pool of this App. 222 | * 223 | */ 224 | ThreadPool& app_thread_pool() noexcept { 225 | return *app_thread_pool_; 226 | } 227 | }; 228 | } -------------------------------------------------------------------------------- /Source/Util/URLEncode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Util.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | extern "C" { 18 | static const uint8_t encode_table_urlcomponent[256] = { 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 00 - 0F 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 - 1F 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x2e, 0x00, // 20 - 2F 22 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 30 - 3F 23 | 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // 40 - 4F 24 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x5f, // 50 - 5F 25 | 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 60 - 6F 26 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x00, 0x00, 0x00, 0x7e, 0x00, // 70 - 7F 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 80 - 8F 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 90 - 9F 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A0 - AF 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B0 - BF 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // C0 - CF 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D0 - DF 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // E0 - EF 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // F0 - FF 35 | }; 36 | 37 | static const uint8_t encode_table_url[256] = { 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 00 - 0F 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 - 1F 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x2d, 0x2e, 0x00, // 20 - 2F 41 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, // 30 - 3F 42 | 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // 40 - 4F 43 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x5f, // 50 - 5F 44 | 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 60 - 6F 45 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x00, 0x00, 0x00, 0x7e, 0x00, // 70 - 7F 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 80 - 8F 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 90 - 9F 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A0 - AF 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B0 - BF 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // C0 - CF 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D0 - DF 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // E0 - EF 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // F0 - FF 54 | }; 55 | 56 | static size_t url_encode(const uint8_t *table, const uint8_t *buf_input, size_t input_size, char *buf_output) { 57 | const char *orig_buf_output = buf_output; 58 | for (size_t pos = 0; pos < input_size; pos++) { 59 | 60 | if (table[*buf_input]) { 61 | sprintf(buf_output, "%c", table[*buf_input]); 62 | buf_output++; 63 | } else { 64 | sprintf(buf_output, "%%%02X", *(uint8_t *) buf_input); 65 | buf_output += 3; 66 | } 67 | 68 | buf_input++; 69 | } 70 | 71 | return buf_output - orig_buf_output; 72 | } 73 | } 74 | 75 | size_t Util::decodeURI(const char *src, size_t src_size, char *dst) { 76 | char *orig_dst = dst; 77 | const char *src_end = src+src_size; 78 | 79 | char a, b; 80 | while (src < src_end) { 81 | if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { 82 | if (a >= 'a') 83 | a -= 'a'-'A'; 84 | if (a >= 'A') 85 | a -= ('A' - 10); 86 | else 87 | a -= '0'; 88 | if (b >= 'a') 89 | b -= 'a'-'A'; 90 | if (b >= 'A') 91 | b -= ('A' - 10); 92 | else 93 | b -= '0'; 94 | 95 | uint8_t c = 16*a+b; 96 | if (c == '=' || c == '&' || c == '+') { 97 | sprintf(dst, "%%%02X", c); 98 | dst += 3; 99 | } else { 100 | *dst++ = c; 101 | } 102 | src+=3; 103 | } else { 104 | *dst++ = *src++; 105 | } 106 | } 107 | 108 | return dst - orig_dst; 109 | } 110 | 111 | size_t Util::decodeURIComponent(const char *src, size_t src_size, char *dst) { 112 | char *orig_dst = dst; 113 | const char *src_end = src+src_size; 114 | 115 | char a, b; 116 | while (src < src_end) { 117 | if ((*src == '%') && 118 | ((a = src[1]) && (b = src[2])) && 119 | (isxdigit(a) && isxdigit(b))) { 120 | if (a >= 'a') 121 | a -= 'a'-'A'; 122 | if (a >= 'A') 123 | a -= ('A' - 10); 124 | else 125 | a -= '0'; 126 | if (b >= 'a') 127 | b -= 'a'-'A'; 128 | if (b >= 'A') 129 | b -= ('A' - 10); 130 | else 131 | b -= '0'; 132 | *dst++ = 16*a+b; 133 | src+=3; 134 | } else { 135 | *dst++ = *src++; 136 | } 137 | } 138 | 139 | return dst - orig_dst; 140 | } 141 | 142 | std::string Util::encodeURI(const std::string_view &__str) { 143 | std::string buf; 144 | buf.resize(__str.size() * 3 + 1); 145 | 146 | auto rc = url_encode(encode_table_url, (uint8_t *)__str.data(), __str.size(), buf.data()); 147 | buf.resize(rc); 148 | return buf; 149 | } 150 | 151 | std::string Util::encodeURIComponent(const std::string_view &__str) { 152 | std::string buf; 153 | buf.resize(__str.size() * 3 + 1); 154 | 155 | auto rc = url_encode(encode_table_urlcomponent, (uint8_t *)__str.data(), __str.size(), buf.data()); 156 | buf.resize(rc); 157 | return buf; 158 | } 159 | 160 | std::string Util::decodeURI(const std::string_view &__str) { 161 | std::string buf; 162 | buf.resize(__str.size()); 163 | 164 | auto rc = decodeURI(__str.data(), __str.size(), buf.data()); 165 | 166 | buf.resize(rc); 167 | return buf; 168 | } 169 | 170 | std::string Util::decodeURIComponent(const std::string_view &__str) { 171 | std::string buf; 172 | buf.resize(__str.size()); 173 | 174 | auto rc = decodeURIComponent(__str.data(), __str.size(), buf.data()); 175 | 176 | buf.resize(rc); 177 | return buf; 178 | } 179 | 180 | -------------------------------------------------------------------------------- /Source/App/Response.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | #include "Buffer.hpp" 18 | 19 | namespace Marisa { 20 | class UpgradedConnection : public Socket { 21 | private: 22 | using Socket::create; 23 | using Socket::listen; 24 | using Socket::bind; 25 | using Socket::connect; 26 | using Socket::accept; 27 | protected: 28 | MHD_UpgradeResponseHandle *urh_ = nullptr; 29 | std::vector extra_data_; 30 | public: 31 | UpgradedConnection(MHD_UpgradeResponseHandle *urh, const void *extbuf, size_t extbuf_len, int fd) : Socket(fd) { 32 | urh_ = urh; 33 | 34 | if (extbuf_len) { 35 | extra_data_.resize(extbuf_len); 36 | memcpy(extra_data_.data(), extbuf, extbuf_len); 37 | } 38 | } 39 | 40 | /** 41 | * Extra data sent by the client during the upgrade process. 42 | * 43 | */ 44 | std::vector extra_data() { 45 | return std::move(extra_data_); 46 | } 47 | 48 | /** 49 | * Close the upgraded connection. 50 | * 51 | */ 52 | void close() noexcept override { 53 | MHD_upgrade_action(urh_, MHD_UPGRADE_ACTION_CLOSE); 54 | } 55 | }; 56 | 57 | class Response { 58 | protected: 59 | void *context = nullptr; 60 | 61 | Buffer output_buffer; 62 | 63 | std::pair, Socket> output_sp; 64 | 65 | bool finalized = false; 66 | 67 | std::optional> time_start; 68 | 69 | void init(); 70 | 71 | void finish_time_measure(MHD_Response *resp); 72 | 73 | static void mhd_upgrade_callback(void *cls, struct MHD_Connection *con, void *con_cls, 74 | const char *extra_in, size_t extra_in_size, MHD_socket sock, struct MHD_UpgradeResponseHandle *urh); 75 | 76 | public: 77 | std::unordered_map header; /*!< HTTP headers */ 78 | int status = 200; /*!< HTTP status code */ 79 | 80 | std::optional upgraded_connection; /*!< Context of upgraded connection */ 81 | 82 | Response(void *__context) : context(__context) { 83 | 84 | } 85 | 86 | /** 87 | * Enable middleware execution time measurement. 88 | * 89 | * Can only be used in normal mode. 90 | * The execution time will be added to the header of response with key "X-Marisa-Execution-Time". 91 | * 92 | */ 93 | void measure_execution_time(); 94 | 95 | /** 96 | * Check if current route is streamed. 97 | * 98 | * @return true for streamed. 99 | * 100 | */ 101 | bool streamed() const noexcept; 102 | 103 | /** 104 | * Write data to client. 105 | * 106 | * In normal mode, the data will be buffered until end(). 107 | * In streamed mode, the data will be sent to client immediately. So you must add all required headers before calling this function. 108 | * 109 | * @param buf Pointer to buffer. 110 | * @param len Length of buffer. 111 | */ 112 | void write(const void *buf, size_t len); 113 | 114 | /** 115 | * Write data to client. 116 | * 117 | * In normal mode, the data will be buffered until end(). 118 | * In streamed mode, the data will be sent to client immediately. So you must add all required headers before calling this function. 119 | * 120 | * @param buf C-style null-terminated string. 121 | */ 122 | void write(const char *buf); 123 | 124 | /** 125 | * Write data to client. 126 | * 127 | * In normal mode, the data will be buffered until end(). 128 | * In streamed mode, the data will be sent to client immediately. So you must add all required headers before calling this function. 129 | * 130 | * @param buf STL-style contiguous container which element size is 1. e.g. std::string, std::vector 131 | */ 132 | template 133 | void write(const T& buf) { 134 | write(buf.data(), buf.size()); 135 | } 136 | 137 | /** 138 | * End transmitting data to client. 139 | * 140 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 141 | * In normal mode, all buffered data will soon be sent to client. 142 | * In streamed mode, the stream will soon be closed. 143 | * 144 | */ 145 | void end(); 146 | 147 | /** 148 | * Send data to client. 149 | * 150 | * A convenient combination of write() and end(). 151 | * 152 | * @param buf Pointer to buffer. 153 | * @param len Length of buffer. 154 | */ 155 | void send(const void *buf, size_t len) { 156 | write(buf, len); 157 | end(); 158 | } 159 | 160 | /** 161 | * Send data to client. 162 | * 163 | * A convenient combination of write() and end(). 164 | * 165 | * @param buf C-style null-terminated string. 166 | */ 167 | void send(const char *buf) { 168 | write(buf); 169 | end(); 170 | } 171 | 172 | /** 173 | * Send data to client. 174 | * 175 | * A convenient combination of write() and end(). 176 | * 177 | * @param buf STL-style contiguous container which element size is 1. e.g. std::string, std::vector 178 | */ 179 | template 180 | void send(const T& buf) { 181 | write(buf); 182 | end(); 183 | } 184 | 185 | /** 186 | * Send predefined status page to client. 187 | * 188 | * Can only be used in normal mode. 189 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 190 | * 191 | * @param code HTTP status code. Use -1 to keep it unchanged. 192 | */ 193 | void send_status_page(int code = -1); 194 | 195 | /** 196 | * Send data in persistent storage to client. 197 | * 198 | * Can only be used in normal mode. 199 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 200 | * 201 | * @param buf Pointer to buffer. 202 | * @param len Length of buffer. 203 | */ 204 | void send_persistent(const void *buf, size_t len); 205 | 206 | /** 207 | * Send data in persistent storage to client. 208 | * 209 | * Can only be used in normal mode. 210 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 211 | * 212 | * @param buf C-style null-terminated string. 213 | */ 214 | void send_persistent(const char *buf); 215 | 216 | /** 217 | * Send file to client. 218 | * 219 | * Can only be used in normal mode. 220 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 221 | * 222 | * @param path Path to the file. 223 | */ 224 | void send_file(std::string_view path); 225 | 226 | /** 227 | * Send upgrade request to client. 228 | * 229 | * Can only be used in normal mode. 230 | * After calling this function, subsequent calls to write() and send*() will be useless. Trailing middlewares will not be executed. 231 | * Can be used to implement websockets. 232 | * 233 | */ 234 | void upgrade(); // TODO: Blocking wait for upgrade done 235 | 236 | ~Response(); 237 | }; 238 | 239 | class ResponseExposed : public Response { 240 | public: 241 | using Response::output_sp; 242 | using Response::init; 243 | using Response::finalized; 244 | }; 245 | 246 | 247 | } -------------------------------------------------------------------------------- /Source/App/Response.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Response.hpp" 14 | #include "Context.hpp" 15 | 16 | using namespace Marisa; 17 | 18 | #define logger (((Context *)context)->logger) 19 | 20 | static const char ModuleName[] = "Response"; 21 | 22 | void Response::init() { 23 | auto *ctx = (Context *)context; 24 | 25 | if (ctx->streamed()) { 26 | output_sp = socket_pair(); 27 | 28 | logger->debug(R"([{} @ {:x}] output_sp fds: {}, {})", ModuleName, (intptr_t) this, output_sp.first.fd(), output_sp.second.fd()); 29 | 30 | output_sp.first.set_nonblocking(); 31 | output_sp.second.set_nonblocking(); 32 | } 33 | } 34 | 35 | void Response::mhd_upgrade_callback(void *cls, struct MHD_Connection *con, void *con_cls, const char *extra_in, size_t extra_in_size, 36 | MHD_socket sock, struct MHD_UpgradeResponseHandle *urh) { 37 | auto *resp_ctx = (Response *)cls; 38 | auto *context = con_cls; 39 | 40 | logger->debug(R"([{} @ {:x}] mhd_upgrade_callback called)", ModuleName, (intptr_t)resp_ctx); 41 | 42 | resp_ctx->upgraded_connection = {urh, extra_in, extra_in_size, sock}; 43 | } 44 | 45 | Response::~Response() { 46 | ///close(output_sp[1]); 47 | } 48 | 49 | void Response::measure_execution_time() { 50 | time_start = std::chrono::high_resolution_clock::now(); 51 | } 52 | 53 | void Response::finish_time_measure(MHD_Response *resp) { 54 | if (time_start) { 55 | auto time_end = std::chrono::high_resolution_clock::now(); 56 | auto duration_ms = (float)std::chrono::duration_cast(time_end - *time_start).count() / 1000; 57 | MHD_add_response_header(resp, "X-Marisa-Execution-Time", std::to_string(duration_ms).c_str()); 58 | } 59 | } 60 | 61 | bool Response::streamed() const noexcept { 62 | auto *ctx = (ContextExposed *)context; 63 | 64 | if (ctx->route) 65 | return ctx->route->mode_streamed; 66 | else 67 | return false; 68 | } 69 | 70 | void Response::write(const void *buf, size_t len) { 71 | logger->debug(R"([{} @ {:x}] write: bufsize: {})", ModuleName, (intptr_t)this, len); 72 | 73 | if (!finalized) { 74 | if (streamed()) { 75 | size_t sent = 0, left = len; 76 | 77 | while (1) { 78 | auto rc_send = output_sp.second.send((uint8_t *)buf+sent, left, MSG_NOSIGNAL); 79 | 80 | logger->debug(R"([{} @ {:x}] write: rc: {}, errno: {})", ModuleName, (intptr_t)this, rc_send, errno); 81 | 82 | if (rc_send > 0) { 83 | sent += rc_send; 84 | left -= rc_send; 85 | logger->debug(R"([{} @ {:x}] write: {}/{}, left: {})", ModuleName, (intptr_t)this, sent, len, left); 86 | 87 | if (!left) { 88 | break; 89 | } 90 | } else if (rc_send == 0) { 91 | logger->debug(R"([{} @ {:x}] write: other end shutdown'd fd)", ModuleName, (intptr_t)this); 92 | finalized = true; 93 | output_sp.second.close(); 94 | break; 95 | } else { 96 | if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) { 97 | logger->debug(R"([{} @ {:x}] write EAGAIN, resume_connection)", ModuleName, (intptr_t)this); 98 | 99 | ((ContextExposed *) context)->resume_connection(); 100 | std::this_thread::yield(); 101 | } else { 102 | logger->error(R"([{} @ {:x}] write errno: {})", ModuleName, (intptr_t)this, errno); 103 | 104 | finalized = true; 105 | try { 106 | output_sp.second.shutdown(); 107 | } catch (std::exception &e) { 108 | logger->warn("[{} @ {:x}] shutdown failed: {}", ModuleName, (intptr_t)this, e.what()); 109 | 110 | } 111 | break; 112 | } 113 | } 114 | } 115 | } else { 116 | output_buffer.insert(buf, len); 117 | } 118 | 119 | logger->debug(R"([{} @ {:x}] write: done)", ModuleName, (intptr_t)this); 120 | 121 | } else { 122 | logger->warn(R"([{} @ {:x}] write() called but already finalized)", ModuleName, (intptr_t)this); 123 | } 124 | } 125 | 126 | void Response::write(const char *buf) { 127 | write(buf, strlen(buf)); 128 | } 129 | 130 | void Response::send_status_page(int code) { 131 | if (code != -1) 132 | status = code; 133 | 134 | auto &page = Util::default_status_page(status); 135 | header["Content-Type"] = "text/html"; 136 | send_persistent(page.c_str(), page.size()); 137 | } 138 | 139 | void Response::send_persistent(const void *buf, size_t len) { 140 | auto *ctx = (ContextExposed *)context; 141 | 142 | if (!finalized) { 143 | if (streamed()) { 144 | throw std::logic_error("send_persistent can't be used in streamed mode"); 145 | } else { 146 | auto *resp = MHD_create_response_from_buffer(len, (void *)buf, MHD_RESPMEM_PERSISTENT); 147 | for (auto &it : header) { 148 | MHD_add_response_header(resp, it.first.c_str(), it.second.c_str()); 149 | } 150 | finish_time_measure(resp); 151 | MHD_queue_response(ctx->mhd_conn, status, resp); 152 | MHD_destroy_response(resp); 153 | ctx->resume_connection(); 154 | finalized = true; 155 | } 156 | } else { 157 | logger->warn(R"([{} @ {:x}] send_persistent() called but already finalized)", ModuleName, (intptr_t)this); 158 | } 159 | } 160 | 161 | void Response::send_persistent(const char *buf) { 162 | send_persistent(buf, strlen(buf)); 163 | } 164 | 165 | void Response::send_file(std::string_view path) { 166 | auto *ctx = (ContextExposed *)context; 167 | 168 | if (!finalized) { 169 | if (streamed()) { 170 | throw std::logic_error("send_file can't be used in streamed mode"); 171 | } else { 172 | int fd = open(path.data(), O_RDONLY); 173 | 174 | if (fd > 0) { 175 | struct stat sbuf; 176 | fstat(fd, &sbuf); 177 | 178 | auto *resp = MHD_create_response_from_fd64(sbuf.st_size, fd); 179 | for (auto &it : header) { 180 | MHD_add_response_header(resp, it.first.c_str(), it.second.c_str()); 181 | } 182 | finish_time_measure(resp); 183 | MHD_queue_response(ctx->mhd_conn, status, resp); 184 | MHD_destroy_response(resp); 185 | ctx->resume_connection(); 186 | 187 | finalized = true; 188 | } else { 189 | throw std::system_error(errno, std::system_category(), "failed to open file"); 190 | } 191 | 192 | } 193 | } else { 194 | logger->warn(R"([{} @ {:x}] send_file() called but already finalized)", ModuleName, (intptr_t)this); 195 | } 196 | } 197 | 198 | void Response::end() { 199 | using namespace std::chrono_literals; 200 | auto *ctx = (ContextExposed *)context; 201 | 202 | if (!finalized) { 203 | if (streamed()) { 204 | output_sp.second.close(); 205 | 206 | size_t rcnt = 0; 207 | while (!ctx->streamed_response_done) { 208 | logger->debug(R"([{} @ {:x}] poll response_done {} times)", ModuleName, (intptr_t)this, rcnt); 209 | ctx->resume_connection(); 210 | std::this_thread::sleep_for(std::chrono::milliseconds((int)pow(2, rcnt))); 211 | rcnt++; 212 | } 213 | 214 | logger->debug(R"([{} @ {:x}] end in stream mode)", ModuleName, (intptr_t)this); 215 | } else { 216 | MHD_Response *resp = nullptr; 217 | if (output_buffer.size()) 218 | resp = MHD_create_response_from_buffer(output_buffer.size(), output_buffer.memory(), MHD_RESPMEM_MUST_FREE); 219 | else 220 | resp = MHD_create_response_from_buffer(0, (void *)"", MHD_RESPMEM_PERSISTENT); 221 | 222 | for (auto &it : header) { 223 | MHD_add_response_header(resp, it.first.c_str(), it.second.c_str()); 224 | } 225 | 226 | finish_time_measure(resp); 227 | MHD_queue_response(ctx->mhd_conn, status, resp); 228 | MHD_destroy_response(resp); 229 | 230 | 231 | ctx->resume_connection(); 232 | 233 | logger->debug(R"([{} @ {:x}] end in normal mode)", ModuleName, (intptr_t)this); 234 | } 235 | 236 | finalized = true; 237 | } else { 238 | logger->warn(R"([{} @ {:x}] end() called but already finalized)", ModuleName, (intptr_t)this); 239 | 240 | } 241 | } 242 | 243 | void Response::upgrade() { 244 | auto *ctx = (ContextExposed *)context; 245 | 246 | if (!finalized) { 247 | if (streamed()) { 248 | throw std::logic_error("upgrade can't be used in streamed mode"); 249 | } else { 250 | auto *resp = MHD_create_response_for_upgrade(&mhd_upgrade_callback, this); 251 | for (auto &it : header) { 252 | MHD_add_response_header(resp, it.first.c_str(), it.second.c_str()); 253 | } 254 | finish_time_measure(resp); 255 | MHD_queue_response(ctx->mhd_conn, status, resp); 256 | MHD_destroy_response(resp); 257 | ctx->resume_connection(); 258 | 259 | finalized = true; 260 | } 261 | } else { 262 | logger->warn(R"([{} @ {:x}] upgrade() called but already finalized)", ModuleName, (intptr_t)this); 263 | } 264 | } 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /Source/App/Request.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "../CommonIncludes.hpp" 16 | 17 | namespace Marisa { 18 | class Request { 19 | public: 20 | struct PostData { 21 | std::string key, value, filename, content_type, transfer_encoding; 22 | }; 23 | protected: 24 | void *context; 25 | 26 | std::string url_, method_, version_; 27 | 28 | std::unordered_map url_vars_; 29 | 30 | std::vector header_keys_cache; 31 | std::unordered_map header_cache; 32 | 33 | std::vector query_keys_cache; 34 | std::unordered_map query_cache; 35 | 36 | std::vector cookie_keys_cache; 37 | std::unordered_map cookie_cache; 38 | 39 | std::vector post_keys_cache; 40 | std::unordered_map post_cache; 41 | 42 | SocketAddress socket_address_cache; 43 | 44 | std::pair, Socket> input_sp; 45 | // int input_sp[2] = {-1, -1}; 46 | bool input_sp_send_end_closed = false; 47 | 48 | const std::function *post_callback_ptr = nullptr; 49 | 50 | MHD_PostProcessor *mhd_pp = nullptr; 51 | 52 | static MHD_Result mhd_header_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 53 | 54 | static MHD_Result mhd_header_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 55 | 56 | static MHD_Result mhd_query_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 57 | 58 | static MHD_Result mhd_query_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 59 | 60 | static MHD_Result mhd_cookie_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 61 | 62 | static MHD_Result mhd_cookie_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value); 63 | 64 | static MHD_Result mhd_post_processor(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, 65 | const char *transfer_encoding, const char *data, uint64_t off, size_t size); 66 | 67 | static MHD_Result mhd_streamed_post_processor(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, 68 | const char *transfer_encoding, const char *data, uint64_t off, size_t size); 69 | 70 | void init(); 71 | public: 72 | /** 73 | * URL of the request. 74 | * 75 | * @return The URL. 76 | * 77 | */ 78 | const std::string& url() noexcept { 79 | return url_; 80 | } 81 | 82 | /** 83 | * Variables in the URL. 84 | * 85 | * If the route specification is not a path expression (e.g. /:foo/:bar), the keys will be the index of smatches, starting from 0. 86 | * 87 | * @return Key-Value pair of the variables. 88 | * 89 | */ 90 | std::unordered_map& url_vars() noexcept { 91 | return url_vars_; 92 | } 93 | 94 | /** 95 | * Method of the request. 96 | * 97 | * @return The method. 98 | * 99 | */ 100 | const std::string& method() noexcept { 101 | return method_; 102 | } 103 | 104 | /** 105 | * HTTP version string of the request. 106 | * 107 | * @return The HTTP version string. 108 | * 109 | */ 110 | const std::string& version() noexcept { 111 | return version_; 112 | } 113 | 114 | /** 115 | * All headers of the request. 116 | * 117 | * @return Key-Value pairs of the headers. 118 | * 119 | */ 120 | const std::unordered_map& header(); 121 | 122 | /** 123 | * Retrieve a specific header value. 124 | * 125 | * Some people like this style of API, so I'll provide one. 126 | * Note that you can't distinguish an empty header from a nonexistent key using this function. 127 | * 128 | * @return Value of the header item. 129 | * 130 | */ 131 | std::string_view header(const std::string& key); 132 | 133 | /** 134 | * All headers keys of the request. In client sequence. 135 | * 136 | * @return Keys of the headers. 137 | * 138 | */ 139 | const std::vector& header_keys(); 140 | 141 | /** 142 | * Query strings in the URL. 143 | * 144 | * @return Key-Value pairs of the headers. 145 | * 146 | */ 147 | const std::unordered_map& query(); 148 | 149 | /** 150 | * Retrieve a specific query string value. 151 | * 152 | * Some people like this style of API, so I'll provide one. 153 | * Note that you can't distinguish an empty header from a nonexistent key using this function. 154 | * 155 | * @return Value of the query string item. 156 | * 157 | */ 158 | std::string_view query(const std::string& key); 159 | 160 | /** 161 | * All query string keys of the request. In client sequence. 162 | * 163 | * @return Keys of the query strings. 164 | * 165 | */ 166 | const std::vector& query_keys(); 167 | 168 | /** 169 | * Cookies of this request. 170 | * 171 | * @return Key-Value pairs of the cookies. 172 | * 173 | */ 174 | const std::unordered_map& cookie(); 175 | 176 | /** 177 | * Retrieve a specific cookie value. 178 | * 179 | * Some people like this style of API, so I'll provide one. 180 | * Note that you can't distinguish an empty header from a nonexistent key using this function. 181 | * 182 | * @return Value of the query string item. 183 | * 184 | */ 185 | std::string_view cookie(const std::string& key); 186 | 187 | /** 188 | * All cookie keys of the request. In client sequence. 189 | * 190 | * @return Keys of the cookies. 191 | * 192 | */ 193 | const std::vector& cookie_keys(); 194 | 195 | /** 196 | * POST data of this request. 197 | * 198 | * This function is only available in normal mode. For streamed mode, use read_post(). 199 | * 200 | * @return Key-Value[s] pairs of the POST data. 201 | * 202 | */ 203 | std::unordered_map& post() noexcept { 204 | return post_cache; 205 | } 206 | 207 | /** 208 | * Retrieve a specific POST data. 209 | * 210 | * Some people like this style of API, so I'll provide one. However this is not efficient. 211 | * 212 | * @return POST data. Empty key for nonexistent. 213 | * 214 | */ 215 | PostData post(const std::string& key); 216 | 217 | /** 218 | * All POST data keys of the request. In client sequence. 219 | * 220 | * This function is only available in normal mode. For streamed mode, use read_post(). 221 | * 222 | * @return Keys of the POST data. 223 | * 224 | */ 225 | std::vector& post_keys() noexcept { 226 | return post_keys_cache; 227 | } 228 | 229 | /** 230 | * Socket address of this connection. 231 | * 232 | * Only useful if the server is not behind a reverse proxy. 233 | * 234 | * @return The socket address. 235 | * 236 | */ 237 | const SocketAddress& socket_address(); 238 | 239 | /** 240 | * Read raw POST data from client. 241 | * 242 | * Only available in streamed mode. 243 | * This is a blocking operation. 244 | * 245 | * @return Raw POST data chunk. Empty for EOF. 246 | * 247 | */ 248 | std::vector read(); 249 | 250 | /** 251 | * Read raw POST data from client, parse it, and use a callback to tell the contents. 252 | * 253 | * Only available in streamed mode. 254 | * This is a blocking operation. 255 | * 256 | * @param callback Callback to retrieve the POST data contents. 257 | * 258 | * @return Indicates if there is more data to be read. 259 | * 260 | */ 261 | bool read_post(const std::function& callback); 262 | 263 | Request(void *__context, const char *__mhd_url, const char *__mhd_method, const char *__mhd_version) : 264 | context(__context), url_(__mhd_url), method_(__mhd_method), version_(__mhd_version) { 265 | 266 | } 267 | 268 | ~Request(); 269 | 270 | }; 271 | 272 | class RequestExposed : public Request { 273 | public: 274 | using Request::mhd_post_processor; 275 | using Request::mhd_pp; 276 | using Request::input_sp; 277 | using Request::input_sp_send_end_closed; 278 | using Request::init; 279 | }; 280 | } -------------------------------------------------------------------------------- /Tests/coverage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | // TODO 14 | 15 | #include 16 | 17 | #include 18 | 19 | using namespace Marisa; 20 | using namespace Middlewares; 21 | using namespace Util; 22 | 23 | int main() { 24 | const char static_arr[] = "Hello Marisa"; 25 | std::vector vec; 26 | vec.insert(vec.end(), static_arr, static_arr+sizeof(static_arr)-1); 27 | App myapp; 28 | 29 | myapp.route("/no_middlewares"); 30 | 31 | myapp.route("/inline_run1").use([func = Simple("Hello Marisa")](auto req, auto rsp, Context *ctx){ 32 | func(req, rsp, ctx); 33 | }); 34 | 35 | myapp.route("/inline_run2").use([](auto req, auto rsp, Context *ctx){ 36 | [](Request *req, auto rsp, Context *ctx){ 37 | std::string a = "aaa"; 38 | 39 | rsp->write(a); 40 | 41 | if (!req->header("Accept").empty()) { 42 | rsp->write("Has Accept header"); 43 | } 44 | 45 | rsp->write("Header keys:"); 46 | 47 | for (auto &it : req->header_keys()) { 48 | rsp->write(it); 49 | rsp->write("\n"); 50 | } 51 | 52 | if (!req->query("aaa").empty()) { 53 | rsp->write("Has aaa query"); 54 | } 55 | 56 | rsp->write("Query keys:"); 57 | 58 | for (auto &it : req->query_keys()) { 59 | rsp->write(it); 60 | rsp->write("\n"); 61 | } 62 | 63 | if (!req->cookie("aaa").empty()) { 64 | rsp->write("Has aaa cookie"); 65 | } 66 | 67 | rsp->write("Cookie keys:"); 68 | 69 | for (auto &it : req->cookie_keys()) { 70 | rsp->write(it); 71 | rsp->write("\n"); 72 | } 73 | 74 | if (!req->post("aaa").key.empty()) { 75 | rsp->write("Has aaa post"); 76 | } 77 | 78 | rsp->write("Post keys:"); 79 | 80 | for (auto &it : req->post_keys()) { 81 | rsp->write(it); 82 | rsp->write("\n"); 83 | } 84 | 85 | rsp->write("Hello Marisa"); 86 | }(req, rsp, ctx); 87 | }); 88 | 89 | auto func = [](Request *request, Response *response, Context *context){ 90 | response->measure_execution_time(); 91 | 92 | std::ostringstream ss; 93 | ss << "\n" 94 | "\n" 95 | << "Usage Demo\n" 96 | "" 130 | ""; 131 | 132 | ss << "" 133 | "

Hello world!

" 134 | "
" 135 | "URL: " << request->url() << "
" 136 | "Method: " << request->method() << "
" 137 | "Socket address: " << request->socket_address().to_string() << "

"; 138 | 139 | 140 | ss << "Headers:
" 141 | "" 142 | "" 143 | "" 144 | "" 145 | ""; 146 | 147 | 148 | for (auto &it : request->header()) { 149 | ss << ""; 150 | ss << ""; 151 | ss << ""; 152 | ss << ""; 153 | } 154 | 155 | 156 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 157 | 158 | ss << "

"; 159 | 160 | ss << "Cookies:
" 161 | "" 162 | "" 163 | "" 164 | "" 165 | ""; 166 | 167 | 168 | for (auto &it : request->cookie()) { 169 | ss << ""; 170 | ss << ""; 171 | ss << ""; 172 | ss << ""; 173 | } 174 | 175 | 176 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 177 | 178 | ss << "

"; 179 | 180 | ss << "URL Variables:
" 181 | << "" 182 | "" 183 | "" 184 | "" 185 | ""; 186 | 187 | 188 | for (auto &it : request->url_vars()) { 189 | ss << ""; 190 | ss << ""; 191 | ss << ""; 192 | ss << ""; 193 | } 194 | 195 | 196 | ss << "
Key/IndexValue
" << it.first << "" << it.second << "
"; 197 | 198 | ss << "

"; 199 | 200 | ss << "URL Query Strings:
" 201 | "" 202 | "" 203 | "" 204 | "" 205 | ""; 206 | 207 | 208 | for (auto &it : request->query()) { 209 | ss << ""; 210 | ss << ""; 211 | ss << ""; 212 | ss << ""; 213 | } 214 | 215 | 216 | ss << "
KeyValue
" << it.first << "" << it.second << "
"; 217 | 218 | ss << "

"; 219 | 220 | ss << "URI encode/decode tests:" << "
"; 221 | ss << ""; 222 | ss << "encodeURI(\"aa=bb&cc=dd大花猫+++\") = \"" << encodeURI("aa=bb&cc=dd大花猫+++") << "\"
"; 223 | ss << "encodeURIComponent(\"aa=bb&cc=dd大花猫+++\") = \"" << encodeURIComponent("aa=bb&cc=dd大花猫+++") << "\"
"; 224 | ss << R"(decodeURI("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") = ")" << decodeURI("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") << "\"
"; 225 | ss << R"(decodeURIComponent("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") = ")" << decodeURIComponent("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") << "\"
"; 226 | ss << R"(decodeURI("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") = ")" << decodeURI("aa%3Dbb%26cc%3Ddd%E5%A4%A7%E8%8A%B1%E7%8C%AB%2B%2B%2B") << "\"
"; 227 | ss << R"(decodeURIComponent("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") = ")" << decodeURIComponent("aa=bb&cc=dd%E5%A4%A7%E8%8A%B1%E7%8C%AB+++") << "\"
"; 228 | ss << "
"; 229 | 230 | // ss << "base64 encode/decode tests:" << "
"; 231 | // ss << ""; 232 | // ss << "Base64::Encoder::encode_once(\"The quick brown fox jumps over the lazy dog\") = \"" << Base64::Encoder::encode_once("The quick brown fox jumps over the lazy dog") << "\"
"; 233 | // ss << "Base64::Decoder::decode_once(\"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw\") = \"" << Base64::Decoder::decode_once("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw") << "\"
"; 234 | // ss << "
"; 235 | 236 | ss << "
Marisa/" << MARISA_VERSION << "\n"; 237 | ss << ""; 238 | 239 | response->send(ss.str()); 240 | }; 241 | 242 | myapp.route("/usage_demo").use(func); 243 | myapp.route("/simple").use(Simple("Hello Marisa")); 244 | myapp.route("/lambda").use([](auto req, auto rsp, auto ctx){ 245 | rsp->send("Hello Marisa"); 246 | }); 247 | 248 | myapp.route("/async_suspend_resume").async().use([state = 0](auto *req, Response *rsp, Context *ctx) mutable { 249 | if (!state) { 250 | std::thread ([&, ctx]{ 251 | sleep(3); 252 | state = 1; 253 | ctx->resume(); 254 | }).detach(); 255 | 256 | ctx->suspend(); 257 | 258 | puts("suspend!"); 259 | } else { 260 | rsp->send("I'm back!"); 261 | } 262 | }); 263 | 264 | myapp.route("/redirect_301").use(Redirection("/simple", 301)); 265 | myapp.route("/redirect_302").use(Redirection("/simple", 302)); 266 | 267 | myapp.route("/error_catcher").use([](auto req, auto rsp, auto ctx){ 268 | rsp->send("Hello Marisa"); 269 | throw std::invalid_argument("what's up doc"); 270 | }); 271 | 272 | 273 | myapp.route("/sendfile").use([](auto req, auto rsp, auto ctx){ 274 | rsp->send_file("/etc/profile"); 275 | }); 276 | 277 | myapp.route("/static_files/**").use(StaticFiles(".", true)); 278 | myapp.route("/static_files2/**").use(StaticFiles("/", true)); 279 | 280 | 281 | myapp.route("/bad_static_files/").use(StaticFiles(".", true)); 282 | 283 | 284 | myapp.route("/streamed_fileupload").stream().use([](Request *req, Response *rsp, auto *ctx) { 285 | if (req->method() == "GET") { 286 | rsp->end(); 287 | } else if (req->method() == "POST") { 288 | std::string encoded; 289 | Base64X::Encoder b64_enc; 290 | 291 | while (req->read_post([&](auto &key, const std::string_view &value, auto &filename, auto &content_type, auto &transfer_encoding) { 292 | if (!value.empty()) { 293 | encoded += b64_enc.encode(value); 294 | } 295 | })); 296 | 297 | encoded += b64_enc.finalize(); 298 | std::cout << "done, encoded size = " << encoded.size() << "\n"; 299 | 300 | rsp->write(encoded); 301 | rsp->end(); 302 | } 303 | }); 304 | 305 | myapp.route(std::regex("^whatever")).use(Simple("whatever!!")); 306 | 307 | 308 | myapp.listen(46878); 309 | myapp.start(); 310 | 311 | sleep(2); 312 | 313 | std::string cmd_get_prefix = "curl -v -H 'Cookie: aaa=bbb;ccc=ddd' -o /dev/null 127.0.0.1:46878/"; 314 | 315 | for (auto &url : {"no_middlewares", "inline_run1", "inline_run2", 316 | "usage_demo", "simple", "lambda", 317 | "async_suspend_resume", 318 | "redirect_301", "redirect_302", "error_catcher", 319 | "sendfile", 320 | "static_files/", "static_files/CoverageTest", "/bad_static_files/", 321 | "static_files/blabla000none", "static_files2/etc/passwd", "static_files2/etc/fstab", 322 | "streamed_fileupload", 323 | "whateveraaa", "whateverbbb", "404"}) { 324 | auto t = cmd_get_prefix + url; 325 | system(t.c_str()); 326 | } 327 | 328 | system(R"(curl -v -X POST -d '{"key1":"value1", "key2":"value2"}' -o /dev/null -H "Content-Type: application/json" 127.0.0.1:46878/inline_run2)"); 329 | // 330 | // system("curl -v -H 'Accept-Encoding: gzip, deflate' 127.0.0.1:46878/noyield_lambda_send_compressed"); 331 | // system("curl -v -H 'Accept-Encoding: deflate, gzip' 127.0.0.1:46878/noyield_lambda_send_compressed"); 332 | // system("curl -v -H 'Accept-Encoding: deflate' 127.0.0.1:46878/noyield_lambda_send_compressed"); 333 | // system("curl -v -H 'Accept-Encoding: gzip' 127.0.0.1:46878/noyield_lambda_send_compressed"); 334 | // 335 | system("curl -v -X POST -F 'data=@CMakeCache.txt' -o /dev/null 127.0.0.1:46878/streamed_fileupload"); 336 | // system("curl -v -X POST -F 'data=@CMakeCache.txt' 127.0.0.1:46878/async_streamed_fileupload"); 337 | // 338 | // system("nc -nv -w 20 127.0.0.1 46878"); 339 | 340 | myapp.stop(); 341 | 342 | return 0; 343 | } -------------------------------------------------------------------------------- /Source/App/Request.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "../CommonIncludes.hpp" 14 | #include "../Util/Util.hpp" 15 | #include "Request.hpp" 16 | #include "Context.hpp" 17 | 18 | 19 | using namespace Marisa; 20 | 21 | #define logger ((Context *)context)->logger 22 | 23 | static const char ModuleName[] = "Request"; 24 | 25 | void Request::init() { 26 | auto *ctx = (Context *)context; 27 | 28 | if (ctx->streamed()) { 29 | input_sp = socket_pair(); 30 | logger->debug(R"([{} @ {:x}] input_sp fds: {}, {})", ModuleName, (intptr_t) this, input_sp.first.fd(), input_sp.second.fd()); 31 | 32 | input_sp.first.set_nonblocking(); 33 | input_sp.second.set_nonblocking(); 34 | } 35 | } 36 | 37 | Request::PostData Request::post(const std::string &key) { 38 | auto it = post_cache.find(key); 39 | 40 | if (it != post_cache.end()) { 41 | return it->second; 42 | } else { 43 | return {}; 44 | } 45 | } 46 | 47 | const std::unordered_map &Request::header() { 48 | if (header_cache.empty()) { 49 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_HEADER_KIND, &mhd_header_cb, this); 50 | } 51 | 52 | return header_cache; 53 | } 54 | 55 | std::string_view Request::header(const std::string& key) { 56 | if (header_cache.empty()) { 57 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_HEADER_KIND, &mhd_header_cb, this); 58 | } 59 | 60 | auto it = header_cache.find(key); 61 | if (it != header_cache.end()) { 62 | return it->second; 63 | } else { 64 | return {}; 65 | } 66 | } 67 | 68 | const std::vector &Request::header_keys() { 69 | if (header_keys_cache.empty()) { 70 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_HEADER_KIND, &mhd_header_key_cb, this); 71 | } 72 | 73 | return header_keys_cache; 74 | } 75 | 76 | const std::unordered_map &Request::query() { 77 | if (query_cache.empty()) { 78 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_GET_ARGUMENT_KIND, &mhd_query_cb, this); 79 | } 80 | 81 | return query_cache; 82 | } 83 | 84 | std::string_view Request::query(const std::string &key) { 85 | if (query_cache.empty()) { 86 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_GET_ARGUMENT_KIND, &mhd_query_cb, this); 87 | } 88 | 89 | auto it = query_cache.find(key); 90 | if (it != query_cache.end()) { 91 | return it->second; 92 | } else { 93 | return {}; 94 | } 95 | } 96 | 97 | const std::vector &Request::query_keys() { 98 | if (query_keys_cache.empty()) { 99 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_GET_ARGUMENT_KIND, &mhd_query_key_cb, this); 100 | } 101 | 102 | return query_keys_cache; 103 | } 104 | 105 | const std::unordered_map &Request::cookie() { 106 | if (cookie_cache.empty()) { 107 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_COOKIE_KIND, &mhd_cookie_cb, this); 108 | } 109 | 110 | return cookie_cache; 111 | } 112 | 113 | std::string_view Request::cookie(const std::string &key) { 114 | if (cookie_cache.empty()) { 115 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_COOKIE_KIND, &mhd_cookie_cb, this); 116 | } 117 | 118 | auto it = cookie_cache.find(key); 119 | if (it != cookie_cache.end()) { 120 | return it->second; 121 | } else { 122 | return {}; 123 | } 124 | } 125 | 126 | const std::vector &Request::cookie_keys() { 127 | if (cookie_keys_cache.empty()) { 128 | MHD_get_connection_values(((ContextExposed *)context)->mhd_conn, MHD_COOKIE_KIND, &mhd_cookie_key_cb, this); 129 | } 130 | 131 | return cookie_keys_cache; 132 | } 133 | 134 | const SocketAddress& Request::socket_address() { 135 | if (socket_address_cache.family() != AddressFamily::IPv4 && socket_address_cache.family() != AddressFamily::IPv6) { 136 | auto *sa = MHD_get_connection_info(((ContextExposed *) context)->mhd_conn,MHD_CONNECTION_INFO_CLIENT_ADDRESS)->client_addr; 137 | 138 | if (sa->sa_family == AF_INET) { 139 | memcpy(socket_address_cache.raw(), sa, sizeof(sockaddr_in)); 140 | } else { 141 | memcpy(socket_address_cache.raw(), sa, sizeof(sockaddr_in6)); 142 | } 143 | } 144 | 145 | return socket_address_cache; 146 | } 147 | 148 | bool Request::read_post( 149 | const std::function &callback) { 150 | post_callback_ptr = &callback; 151 | 152 | if (!mhd_pp) 153 | mhd_pp = MHD_create_post_processor(((ContextExposed *)context)->mhd_conn, 4096, &mhd_streamed_post_processor, this); 154 | 155 | auto buf = read(); 156 | if (buf.empty()) { 157 | logger->debug("[{} @ {:x}] read_post end", ModuleName, (intptr_t)this); 158 | 159 | if (mhd_pp) { 160 | MHD_destroy_post_processor(mhd_pp); 161 | mhd_pp = nullptr; 162 | } 163 | return false; 164 | } 165 | 166 | MHD_post_process(mhd_pp, reinterpret_cast(buf.data()), buf.size()); 167 | 168 | return true; 169 | } 170 | 171 | std::vector Request::read() { 172 | std::vector ret(4096); 173 | 174 | while (1) { 175 | auto rc_recv = input_sp.second.recv(ret.data(), ret.size(), 0); 176 | 177 | if (rc_recv > 0) { 178 | input_sp.second.set_nonblocking(); 179 | ret.resize(rc_recv); 180 | break; 181 | } else if (rc_recv == 0) { 182 | ret.resize(0); 183 | try { 184 | input_sp.second.shutdown(); 185 | } catch (std::exception &e) { 186 | logger->warn("[{} @ {:x}] shutdown failed: {}", ModuleName, (intptr_t)this, e.what()); 187 | 188 | } 189 | break; 190 | } else { 191 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { 192 | input_sp.second.set_nonblocking(false); 193 | 194 | ((ContextExposed *) context)->resume_connection(); 195 | // std::this_thread::yield(); 196 | } else { 197 | ret.resize(0); 198 | break; 199 | } 200 | } 201 | 202 | } 203 | 204 | logger->trace("[{} @ {:x}] read={}", ModuleName, (intptr_t)this, ret.size()); 205 | 206 | return ret; 207 | } 208 | 209 | Request::~Request() { 210 | ///close(input_sp[1]); 211 | } 212 | 213 | MHD_Result Request::mhd_header_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 214 | auto *ctx = (Request *)cls; 215 | auto *context = ctx->context; 216 | 217 | logger->trace("[{} @ {:x}] mhd_cookie_key_cb: kind: {}, key: {}@{}, value: {}@{}", 218 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 219 | 220 | ctx->header_cache.insert({key, value}); 221 | 222 | return MHD_YES; 223 | } 224 | 225 | MHD_Result Request::mhd_header_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 226 | auto *ctx = (Request *)cls; 227 | auto *context = ctx->context; 228 | 229 | logger->trace("[{} @ {:x}] mhd_header_key_cb: kind: {}, key: {}@{}, value: {}@{}", 230 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 231 | 232 | ctx->header_keys_cache.emplace_back(key); 233 | 234 | return MHD_YES; 235 | } 236 | 237 | MHD_Result Request::mhd_query_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 238 | auto *ctx = (Request *)cls; 239 | auto *context = ctx->context; 240 | 241 | logger->trace("[{} @ {:x}] mhd_query_cb: kind: {}, key: {}@{}, value: {}@{}", 242 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 243 | 244 | ctx->query_cache.insert({key, value}); 245 | 246 | return MHD_YES; 247 | } 248 | 249 | MHD_Result Request::mhd_query_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 250 | auto *ctx = (Request *)cls; 251 | auto *context = ctx->context; 252 | 253 | logger->trace("[{} @ {:x}] mhd_query_key_cb: kind: {}, key: {}@{}, value: {}@{}", 254 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 255 | 256 | ctx->query_keys_cache.emplace_back(key); 257 | 258 | return MHD_YES; 259 | } 260 | 261 | MHD_Result Request::mhd_cookie_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 262 | auto *ctx = (Request *)cls; 263 | auto *context = ctx->context; 264 | 265 | logger->trace("[{} @ {:x}] mhd_cookie_cb: kind: {}, key: {}@{}, value: {}@{}", 266 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 267 | 268 | ctx->cookie_cache.insert({key, value}); 269 | 270 | return MHD_YES; 271 | } 272 | 273 | MHD_Result Request::mhd_cookie_key_cb(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { 274 | auto *ctx = (Request *)cls; 275 | auto *context = ctx->context; 276 | 277 | logger->trace("[{} @ {:x}] mhd_cookie_key_cb: kind: {}, key: {}@{}, value: {}@{}", 278 | ModuleName, (intptr_t)ctx, kind, key, (intptr_t)key, value, (intptr_t)value); 279 | 280 | ctx->cookie_keys_cache.emplace_back(key); 281 | 282 | return MHD_YES; 283 | } 284 | 285 | MHD_Result 286 | Request::mhd_post_processor(void *cls, enum 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) { 287 | auto *ctx = (Request *)cls; 288 | auto *context = ctx->context; 289 | 290 | if (key) { 291 | if (!ctx->post_keys_cache.empty() && ctx->post_keys_cache.back() != key) { 292 | ctx->post_keys_cache.emplace_back(key); 293 | } 294 | 295 | auto &pd = ctx->post_cache[key]; 296 | 297 | if (size) 298 | pd.value.insert(pd.value.end(), data, data + size); 299 | 300 | if (content_type && pd.content_type.empty()) 301 | pd.content_type = content_type; 302 | 303 | if (transfer_encoding && pd.transfer_encoding.empty()) 304 | pd.transfer_encoding = transfer_encoding; 305 | } 306 | 307 | logger->trace("[{} @ {:x}] mhd_post_processor: kind: {}, key: {}, filename: {}, content_type: {}, transfer_encoding: {}, data: {}, off: {}, size: {}", 308 | ModuleName, (intptr_t)ctx, kind, key, filename, content_type, transfer_encoding, data, off, size); 309 | 310 | 311 | return MHD_YES; 312 | } 313 | 314 | MHD_Result Request::mhd_streamed_post_processor(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, 315 | const char *data, uint64_t off, size_t size) { 316 | auto *ctx = (Request *)cls; 317 | auto *context = ctx->context; 318 | 319 | (*ctx->post_callback_ptr)(key ? key : std::string_view(), 320 | size ? std::string_view(data, size) : std::string_view(), 321 | filename ? filename : std::string_view(), 322 | content_type ? content_type : std::string_view(), 323 | transfer_encoding ? transfer_encoding : std::string_view()); 324 | 325 | logger->trace("[{} @ {:x}] mhd_streamed_post_processor: kind: {}, key: {}, filename: {}, content_type: {}, transfer_encoding: {}, data: {}, off: {}, size: {}", 326 | ModuleName, (intptr_t)ctx, kind, key, filename, content_type, transfer_encoding, data, off, size); 327 | 328 | 329 | return MHD_YES; 330 | } 331 | -------------------------------------------------------------------------------- /Source/App/App.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "App.hpp" 14 | #include "Response.hpp" 15 | #include "../Util/Util.hpp" 16 | 17 | using namespace Marisa; 18 | 19 | const char ModuleName[] = "App"; 20 | 21 | void App::ignore_sigpipe() { 22 | signal(SIGPIPE, SIG_IGN); 23 | } 24 | 25 | void App::init() { 26 | init_logger(); 27 | logger_internal->info("Marisa Version " MARISA_VERSION); 28 | } 29 | 30 | void App::init_logger() { 31 | // I know this is stupid, but why spdlog uses a initializer list for sinks? 32 | 33 | auto *env_ll_internal = getenv("MARISA_LOGLEVEL_INTERNAL"); 34 | auto *env_ll_access = getenv("MARISA_LOGLEVEL_ACCESS"); 35 | 36 | if (env_ll_internal) { 37 | config.logging.internal.level = static_cast(strtol(env_ll_internal, nullptr, 10)); 38 | } 39 | 40 | if (env_ll_access) { 41 | config.logging.access.level = static_cast(strtol(env_ll_access, nullptr, 10)); 42 | } 43 | 44 | // Internal 45 | if (config.logging.internal.stdout_enabled) { 46 | spdlog_internal_sink_stdout = std::make_shared(); 47 | spdlog_internal_sink_stdout->set_level(config.logging.internal.level); 48 | spdlog_internal_sink_stdout->set_pattern(config.logging.internal.pattern); 49 | } 50 | 51 | if (!config.logging.internal.file.empty()) { 52 | spdlog_internal_sink_file = std::make_shared(config.logging.internal.file, 0, 0); 53 | spdlog_internal_sink_file->set_level(config.logging.internal.level); 54 | spdlog_internal_sink_file->set_pattern(config.logging.internal.pattern); 55 | } 56 | 57 | if (spdlog_internal_sink_stdout && !spdlog_internal_sink_file) { 58 | logger_internal = std::make_unique(spdlog::logger("marisa_internal", {spdlog_internal_sink_stdout})); 59 | } else if (!spdlog_internal_sink_stdout && spdlog_internal_sink_file) { 60 | logger_internal = std::make_unique(spdlog::logger("marisa_internal", {spdlog_internal_sink_file})); 61 | } else if (spdlog_internal_sink_stdout && spdlog_internal_sink_file) { 62 | logger_internal = std::make_unique(spdlog::logger("marisa_internal", {spdlog_internal_sink_stdout, spdlog_internal_sink_file})); 63 | } else { 64 | throw std::logic_error("both log targets can't be disabled at the same time. set log level to off instead."); 65 | } 66 | 67 | logger_internal->set_level(config.logging.internal.level); 68 | 69 | // Access 70 | if (config.logging.access.stdout_enabled) { 71 | spdlog_access_sink_stdout = std::make_shared(); 72 | spdlog_access_sink_stdout->set_level(config.logging.access.level); 73 | spdlog_access_sink_stdout->set_pattern(config.logging.access.pattern); 74 | } 75 | 76 | if (!config.logging.access.file.empty()) { 77 | spdlog_access_sink_file = std::make_shared(config.logging.access.file, 0, 0); 78 | spdlog_access_sink_file->set_level(config.logging.access.level); 79 | spdlog_access_sink_file->set_pattern(config.logging.access.pattern); 80 | } 81 | 82 | if (spdlog_access_sink_stdout && !spdlog_access_sink_file) { 83 | logger_access = std::make_unique(spdlog::logger("marisa_access", {spdlog_access_sink_stdout})); 84 | } else if (!spdlog_access_sink_stdout && spdlog_access_sink_file) { 85 | logger_access = std::make_unique(spdlog::logger("marisa_access", {spdlog_access_sink_file})); 86 | } else if (spdlog_access_sink_stdout && spdlog_access_sink_file) { 87 | logger_access = std::make_unique(spdlog::logger("marisa_access", {spdlog_access_sink_stdout, spdlog_access_sink_file})); 88 | } else { 89 | throw std::logic_error("both log targets can't be disabled at the same time. set log level to off instead."); 90 | } 91 | 92 | logger_access->set_level(config.logging.access.level); 93 | } 94 | 95 | MHD_Result App::mhd_connection_handler(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { 96 | 97 | auto *app = (App *)cls; 98 | 99 | app->logger_internal->debug("[{} @ {:x}] mhd_connection_handler: *con_cls={}", ModuleName, (intptr_t)app, (intptr_t)*con_cls); 100 | 101 | if (!*con_cls) { // First time 102 | app->logger_internal->debug("[{} @ {:x}] first time", ModuleName, (intptr_t)app); 103 | 104 | *con_cls = new Context(app, connection, url, method, version); 105 | auto *ctx = (ContextExposed *)*con_cls; 106 | 107 | ctx->process_request(); 108 | app->logger_internal->debug("[{} @ {:x}] request processed", ModuleName, (intptr_t)app); 109 | return MHD_YES; 110 | } else { // Second or more time 111 | app->logger_internal->debug("[{} @ {:x}] second or more time", ModuleName, (intptr_t)app); 112 | 113 | auto *ctx = (ContextExposed *)*con_cls; 114 | 115 | if (ctx->response.streamed()) { 116 | if (!ctx->app_started) { 117 | // ctx->suspend_connection(); 118 | ctx->start_app(); 119 | app->logger_internal->debug("[{} @ {:x}] start_app called in STREAMED mode", ModuleName, (intptr_t)app); 120 | } 121 | 122 | auto &socket_input = ((RequestExposed *)&ctx->request)->input_sp.first; 123 | 124 | if (*upload_data_size) { 125 | app->logger_internal->debug("[{} @ {:x}] has post data, size={}", ModuleName, (intptr_t)app, *upload_data_size); 126 | 127 | 128 | auto rc_send = socket_input.send(upload_data, *upload_data_size, MSG_NOSIGNAL); 129 | 130 | app->logger_internal->trace("[{} @ {:x}] rc_send={}", ModuleName, (intptr_t)app, rc_send); 131 | 132 | if (rc_send > 0) { 133 | *upload_data_size -= rc_send; 134 | if (*upload_data_size) { 135 | ctx->suspend_connection(); 136 | } 137 | return MHD_YES; 138 | } else if (rc_send == 0) { 139 | app->logger_internal->debug("[{} @ {:x}] rc_send returned 0 (other end close), terminating connection", ModuleName, (intptr_t)app); 140 | return MHD_NO; 141 | } else { 142 | if (errno == EWOULDBLOCK || errno == EAGAIN) { 143 | app->logger_internal->debug("[{} @ {:x}] rc_send errno is EAGAIN, suspend connection", ModuleName, (intptr_t)app); 144 | ctx->suspend_connection(); 145 | return MHD_YES; 146 | } else if (errno == EINTR) { 147 | return MHD_YES; 148 | } else { 149 | app->logger_internal->debug("[{} @ {:x}] rc_send errno is {}, terminating connection", ModuleName, (intptr_t)app, errno); 150 | return MHD_NO; 151 | } 152 | } 153 | } else { 154 | app->logger_internal->debug("[{} @ {:x}] no post data left", ModuleName, (intptr_t)app); 155 | 156 | if (((RequestExposed *)&ctx->request)->input_sp_send_end_closed) { 157 | app->logger_internal->debug("[{} @ {:x}] fd_input ALREADY shutdown", ModuleName, (intptr_t)app); 158 | } else { 159 | try { 160 | socket_input.shutdown(); 161 | app->logger_internal->debug("[{} @ {:x}] fd_input shutdown", ModuleName, (intptr_t) app); 162 | } catch (std::exception &e) { 163 | app->logger_internal->warn("[{} @ {:x}] shutdown failed: {}", ModuleName, (intptr_t) app, e.what()); 164 | 165 | } 166 | // close(fd_input); 167 | ((RequestExposed *)&ctx->request)->input_sp_send_end_closed = true; 168 | } 169 | 170 | // if (ctx->response.stream_started()) { 171 | // puts("stream started"); 172 | auto *resp = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4096, 173 | &App::mhd_streamed_response_reader, 174 | ctx, 175 | &App::mhd_streamed_response_read_done); 176 | for (auto &it : ctx->response.header) { 177 | MHD_add_response_header(resp, it.first.c_str(), it.second.c_str()); 178 | } 179 | MHD_queue_response(connection, ctx->response.status, resp); 180 | MHD_destroy_response(resp); 181 | return MHD_YES; 182 | // } else { 183 | // puts("stream not started"); 184 | //// ctx->suspend_connection(); 185 | // return MHD_YES; 186 | // } 187 | } 188 | } else { // Not streamed 189 | if (*upload_data_size) { // Read all post data before starting app 190 | ctx->processed_post_size += *upload_data_size; 191 | if (ctx->processed_post_size > app->config.connection.max_post_size) { 192 | app->logger_internal->debug("[{} @ {:x}] POST exceeded size in normal mode, terminating connection", ModuleName, (intptr_t)app); 193 | 194 | return MHD_NO; 195 | } 196 | 197 | auto &req = static_cast(ctx->request); 198 | 199 | if (!req.mhd_pp) { 200 | req.mhd_pp = MHD_create_post_processor(connection, 1024, &Marisa::RequestExposed::mhd_post_processor, &req); 201 | } 202 | 203 | MHD_post_process(req.mhd_pp, upload_data, *upload_data_size); 204 | 205 | *upload_data_size = 0; 206 | 207 | return MHD_YES; 208 | } else { // No post data left 209 | if (!ctx->app_started) { 210 | ctx->suspend_connection(); 211 | ctx->start_app(); 212 | app->logger_internal->debug("[{} @ {:x}] start_app called in NORMAL mode", ModuleName, (intptr_t)app); 213 | 214 | } 215 | return MHD_YES; 216 | } 217 | } 218 | } 219 | 220 | } 221 | 222 | ssize_t App::mhd_streamed_response_reader(void *cls, uint64_t pos, char *buf, size_t max) { 223 | auto *ctx = (ContextExposed *)cls; 224 | 225 | auto &sock = ((ResponseExposed *)&ctx->response)->output_sp.first; 226 | 227 | auto rc_recv = sock.recv(buf, max, 0); 228 | 229 | ctx->app->logger_internal->debug(R"([{} @ {:x}] streamed_response_reader: cls={:x}, fd={}, pos={}, rc_recv={})", ModuleName, (intptr_t)ctx->app, (intptr_t)cls, sock.fd(), pos, rc_recv); 230 | 231 | // printf("streamed_response_reader: cls=%p, fd=%d, rc_recv=%ld\n", cls, fd, rc_recv); 232 | 233 | if (rc_recv > 0) { 234 | return rc_recv; 235 | } else if (rc_recv == 0) { 236 | ctx->streamed_response_done = true; 237 | ctx->app->logger_internal->debug(R"([{} @ {:x}] streamed_response_reader: all data read)", ModuleName, (intptr_t)ctx->app); 238 | return MHD_CONTENT_READER_END_OF_STREAM; 239 | } else { 240 | if (errno == EWOULDBLOCK || errno == EAGAIN) { 241 | ctx->app->logger_internal->debug(R"([{} @ {:x}] streamed_response_reader: read EAGAIN)", ModuleName, (intptr_t)ctx->app); 242 | ctx->suspend_connection(); 243 | return 0; 244 | } else if (errno == EINTR) { 245 | return 0; 246 | } else { 247 | ctx->app->logger_internal->error(R"([{} @ {:x}] streamed_response_reader: read errno: {})", ModuleName, (intptr_t)ctx->app, errno); 248 | return MHD_CONTENT_READER_END_WITH_ERROR; 249 | } 250 | } 251 | } 252 | 253 | void App::mhd_streamed_response_read_done(void *cls) { 254 | auto *ctx = (ContextExposed *)cls; 255 | // auto &sock = ((ResponseContextExposed *)&ctx->response)->output_sp.first; 256 | try { 257 | ((ResponseExposed *) &ctx->response)->output_sp.first.shutdown(); 258 | } catch (std::exception &e) { 259 | ctx->app->logger_internal->warn("[{} @ {:x}] streamed_response_read_done: shutdown failed: {}, are you using MacOS??", ModuleName, (intptr_t)ctx->app, e.what()); 260 | } 261 | 262 | ctx->app->logger_internal->info("[{} @ {:x}] streamed_response_read_done: ctx={}, fd={}", ModuleName, (intptr_t)ctx->app, cls, 263 | ((ResponseExposed *)&ctx->response)->output_sp.first.fd()); 264 | 265 | } 266 | 267 | void App::mhd_request_completed(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { 268 | 269 | auto *app = (App *)cls; 270 | auto *ctx = (ContextExposed *)(*con_cls); 271 | 272 | if (ctx) { 273 | // Ensure socketpairs are disconnected when in streamed mode 274 | // In case of improper connection shutdown, such as client disconnected in middle of sending post data 275 | if (ctx->streamed()) { 276 | auto &fd_input_ref = ((RequestExposed *) &ctx->request)->input_sp; 277 | auto &fd_output_ref = ((ResponseExposed *) &ctx->response)->output_sp; 278 | 279 | // Prevent App thread from stuck at socketpair I/O 280 | fd_input_ref.first.shutdown(); 281 | fd_output_ref.first.shutdown(); 282 | 283 | // Prevent App thread from infinite looping at end() 284 | ctx->streamed_response_done = true; 285 | } 286 | 287 | delete ctx; 288 | *con_cls = nullptr; 289 | 290 | app->logger_internal->debug("[{} @ {:x}] request completed", ModuleName, (intptr_t)app); 291 | } 292 | 293 | } 294 | 295 | Route &App::route(const std::string &expression) { 296 | if (expression.empty()) 297 | throw std::invalid_argument("route path can't be an empty string"); 298 | 299 | if (expression == "*") { 300 | route_global_ = std::make_shared(); 301 | return *route_global_; 302 | } 303 | 304 | // if (__route[0] != '/') 305 | // throw std::invalid_argument("non-global route path must begin with a slash '/'"); 306 | 307 | auto route_sptr = std::make_shared(); 308 | 309 | std::string route_regex_str; 310 | 311 | if (expression.find(':') != std::string::npos) { 312 | logger_internal->debug(R"([{} @ {:x}] route contains variables)", ModuleName, (intptr_t)this); 313 | 314 | auto p = ReGlob::PathResolve(expression); 315 | route_regex_str = ReGlob::RegexpString(p.first, {.capture = true}, true); 316 | std::static_pointer_cast(route_sptr)->path_keys = std::move(p.second); 317 | } else { 318 | route_regex_str = ReGlob::RegexpString(expression, { 319 | .bash_syntax = true, 320 | .full_match = true, 321 | .capture = true, 322 | }); 323 | } 324 | 325 | std::regex route_regex(route_regex_str, std::regex::ECMAScript | std::regex::optimize); 326 | 327 | logger_internal->debug(R"([{} @ {:x}] adding route "{}": regex="{}", ptr={:x})", ModuleName, (intptr_t)this, expression, route_regex_str, (uintptr_t)route_sptr.get()); 328 | 329 | route_mapping.emplace_back(std::move(route_regex), route_sptr); 330 | 331 | return *route_sptr; 332 | } 333 | 334 | Route &App::route(std::regex regexp) { 335 | auto route_sptr = std::make_shared(); 336 | route_mapping.emplace_back(std::move(regexp), route_sptr); 337 | 338 | return *route_sptr; 339 | } 340 | 341 | void App::listen(uint16_t port, bool ipv6_enabled) { 342 | if (ipv6_enabled) { 343 | listen_addr.as_ipv6()->from_string("::"); 344 | listen_addr.as_ipv6()->port() = port; 345 | listen_addr.family() = AddressFamily::IPv6; 346 | 347 | mhd_flags |= MHD_USE_DUAL_STACK; 348 | } else { 349 | listen_addr.as_ipv4()->from_string("0.0.0.0"); 350 | listen_addr.as_ipv4()->port() = port; 351 | listen_addr.family() = AddressFamily::IPv4; 352 | 353 | mhd_flags &= ~MHD_USE_DUAL_STACK; 354 | } 355 | } 356 | 357 | void App::listen_v4(const std::string &address, uint16_t port) { 358 | listen_addr.as_ipv4()->from_string(address); 359 | listen_addr.as_ipv4()->port() = port; 360 | listen_addr.family() = AddressFamily::IPv4; 361 | } 362 | 363 | void App::listen_v6(const std::string &address, uint16_t port) { 364 | listen_addr.as_ipv6()->from_string(address); 365 | listen_addr.as_ipv6()->port() = port; 366 | listen_addr.family() = AddressFamily::IPv6; 367 | } 368 | 369 | void App::set_https_cert(const std::string &str) { 370 | https_cert = str; 371 | mhd_flags |= MHD_USE_TLS; 372 | } 373 | 374 | void App::set_https_cert_file(const std::string &path) { 375 | set_https_cert(Util::read_file(path)); 376 | } 377 | 378 | void App::set_https_key(const std::string &str) { 379 | https_key = str; 380 | mhd_flags |= MHD_USE_TLS; 381 | } 382 | 383 | void App::set_https_key_file(const std::string &path) { 384 | set_https_key(Util::read_file(path)); 385 | } 386 | 387 | void App::set_https_key_passwd(const std::string &str) { 388 | https_key_passwd = str; 389 | } 390 | 391 | void App::set_https_trust(const std::string &str) { 392 | https_trust = str; 393 | } 394 | 395 | void App::set_https_trust_file(const std::string &path) { 396 | set_https_trust(Util::read_file(path)); 397 | } 398 | 399 | App::App() { 400 | init(); 401 | } 402 | 403 | App::App(App::Config cfg) { 404 | config = std::move(cfg); 405 | init(); 406 | } 407 | 408 | void App::start(ssize_t io_thread_count) { 409 | if (listen_addr.family() != AddressFamily::IPv4 && listen_addr.family() != AddressFamily::IPv6) { 410 | throw std::invalid_argument("Please set a listening address before starting server"); 411 | } 412 | 413 | unsigned long hw_cc = std::thread::hardware_concurrency(); 414 | logger_internal->info("Your machine's hardware concurrency is {}", hw_cc); 415 | 416 | if (io_thread_count == -1) { 417 | io_thread_count = hw_cc; 418 | } 419 | 420 | unsigned long app_thread_count = 1; 421 | 422 | for (auto &it : route_mapping) { 423 | if (!((RouteExposed *)it.second.get())->mode_async) { 424 | app_thread_count = hw_cc * config.app.thread_pool_size_ratio; 425 | logger_internal->info("Using {} App threads for non-async middlewares", app_thread_count); 426 | break; 427 | } 428 | } 429 | 430 | if (config.ignore_sigpipe) 431 | ignore_sigpipe(); 432 | 433 | std::vector mhd_options; 434 | 435 | if (!https_cert.empty()) { 436 | mhd_options.emplace_back(MHD_OptionItem{MHD_OPTION_HTTPS_MEM_CERT, 0, (void *)https_cert.c_str()}); 437 | } 438 | 439 | if (!https_key.empty()) { 440 | mhd_options.emplace_back(MHD_OptionItem{MHD_OPTION_HTTPS_MEM_KEY, 0, (void *)https_key.c_str()}); 441 | } 442 | 443 | if (!https_key_passwd.empty()) { 444 | mhd_options.emplace_back(MHD_OptionItem{MHD_OPTION_HTTPS_KEY_PASSWORD, 0, (void *)https_key_passwd.c_str()}); 445 | } 446 | 447 | if (!https_trust.empty()) { 448 | mhd_options.emplace_back(MHD_OptionItem{MHD_OPTION_HTTPS_MEM_TRUST, 0, (void *)https_trust.c_str()}); 449 | } 450 | 451 | mhd_options.emplace_back(MHD_OptionItem{MHD_OPTION_END, 0, nullptr}); 452 | 453 | logger_internal->info("Starting server on {} with {} I/O threads ...", listen_addr.to_string(), io_thread_count); 454 | logger_internal->debug("[{} @ {:x}] start: mhd_flags=0x{:x}", ModuleName, (intptr_t)this, mhd_flags); 455 | 456 | app_thread_pool_ = std::make_unique(app_thread_count); 457 | 458 | mhd_daemon = MHD_start_daemon(mhd_flags, 0, 459 | nullptr, nullptr, 460 | &mhd_connection_handler, this, 461 | MHD_OPTION_NOTIFY_COMPLETED, &mhd_request_completed, this, 462 | MHD_OPTION_SOCK_ADDR, listen_addr.raw(), 463 | MHD_OPTION_THREAD_POOL_SIZE, io_thread_count, 464 | MHD_OPTION_CONNECTION_TIMEOUT, config.connection.timeout_seconds, 465 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128 * 1024, 466 | MHD_OPTION_CONNECTION_MEMORY_INCREMENT, 8192, 467 | MHD_OPTION_CONNECTION_LIMIT, config.connection.max_connections, 468 | MHD_OPTION_ARRAY, mhd_options.data(), 469 | MHD_OPTION_END); 470 | 471 | logger_internal->debug("[{} @ {:x}] start: mhd_daemon=0x{:x}", ModuleName, (intptr_t)this, (intptr_t)mhd_daemon); 472 | 473 | if (!mhd_daemon) { 474 | throw std::logic_error("Failed to start server"); 475 | } 476 | 477 | logger_internal->info("Server successfully started"); 478 | 479 | } 480 | 481 | void App::stop() { 482 | logger_internal->info("Stopping server on {} ...", listen_addr.to_string()); 483 | 484 | if (mhd_daemon) { 485 | MHD_stop_daemon(mhd_daemon); 486 | mhd_daemon = nullptr; 487 | logger_internal->info("Server successfully stopped"); 488 | } else { 489 | logger_internal->info("Server already stopped"); 490 | } 491 | } 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | -------------------------------------------------------------------------------- /Source/Util/MimeTypes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Marisa. 3 | Copyright (C) 2015-2021 ReimuNotMoe 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the MIT License. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include "Util.hpp" 14 | 15 | using namespace Marisa; 16 | 17 | static const std::string unknown_mime_type = "application/octet-stream"; 18 | 19 | static std::unordered_map mime_types = { 20 | {"3ds", "image/x-3ds"}, 21 | {"3g2", "video/3gpp2"}, 22 | {"3ga", "video/3gpp"}, 23 | {"3gp2", "video/3gpp2"}, 24 | {"3gpp2", "video/3gpp2"}, 25 | {"3gpp", "video/3gpp"}, 26 | {"3gp", "video/3gpp"}, 27 | {"aa", "audio/x-pn-audibleaudio"}, 28 | {"aac", "audio/aac"}, 29 | {"a", "application/x-archive"}, 30 | {"aax", "audio/x-pn-audibleaudio"}, 31 | {"abc", "text/vnd.abc"}, 32 | {"abw", "application/x-abiword"}, 33 | {"ac3", "audio/ac3"}, 34 | {"ace", "application/x-ace"}, 35 | {"adb", "text/x-adasrc"}, 36 | {"adf", "application/x-amiga-disk-format"}, 37 | {"ads", "text/x-adasrc"}, 38 | {"afm", "application/x-font-afm"}, 39 | {"agb", "application/x-gba-rom"}, 40 | {"ag", "image/x-applix-graphics"}, 41 | {"ai", "application/illustrator"}, 42 | {"aif", "audio/x-aiff"}, 43 | {"aifc", "audio/x-aifc"}, 44 | {"aiff", "audio/x-aiff"}, 45 | {"aiffc", "audio/x-aifc"}, 46 | {"al", "application/x-perl"}, 47 | {"alz", "application/x-alz"}, 48 | {"amr", "audio/amr"}, 49 | {"amz", "audio/x-am"}, 50 | {"ani", "application/x-navi-animation"}, 51 | {"anx", "application/annodex"}, 52 | {"apc", "application/x-etherpeek"}, 53 | {"ape", "audio/x-ape"}, 54 | {"apk", "application/vnd.android.package-archive"}, 55 | {"appimage", "application/vnd.appimage"}, 56 | {"ar", "application/x-archive"}, 57 | {"arj", "application/x-arj"}, 58 | {"arw", "image/x-sony-arw"}, 59 | {"as", "application/x-applix-spreadsheet"}, 60 | {"ascii85", "text/x-ascii85"}, 61 | {"asc", "text/plain"}, 62 | {"asf", "application/vnd.ms-asf"}, 63 | {"asp", "application/x-asp"}, 64 | {"ass", "text/x-ssa"}, 65 | {"asx", "audio/x-ms-asx"}, 66 | {"atom", "application/atom"}, 67 | {"au", "audio/basic"}, 68 | {"automount", "text/x-systemd-unit"}, 69 | {"avf", "video/x-msvideo"}, 70 | {"avi", "video/x-msvideo"}, 71 | {"aw", "application/x-applix-word"}, 72 | {"awb", "audio/amr-wb"}, 73 | {"awk", "application/x-awk"}, 74 | {"awp", "application/x-accountwizard-package"}, 75 | {"axa", "audio/annodex"}, 76 | {"axv", "video/annodex"}, 77 | {"b2a", "application/btoa"}, 78 | {"b64", "application/base64"}, 79 | {"bak", "application/x-trash"}, 80 | {"base32", "text/x-base32"}, 81 | {"base64", "application/base64"}, 82 | {"bay", "image/x-kde-raw"}, 83 | {"bcpio", "application/x-bcpio"}, 84 | {"bdf", "application/x-font-bdf"}, 85 | {"bdm", "video/mp2t"}, 86 | {"bdmv", "video/mp2t"}, 87 | {"bfr", "application/x-netinstobserver"}, 88 | {"bib", "text/x-bibtex"}, 89 | {"bin", "application/octet-stream"}, 90 | {"blend", "application/x-blender"}, 91 | {"blender", "application/x-blender"}, 92 | {"bmp", "image/bmp"}, 93 | {"bmq", "image/x-kde-raw"}, 94 | {"bsdiff", "application/x-bsdiff"}, 95 | {"btoa", "application/btoa"}, 96 | {"bz2", "application/x-bzip"}, 97 | {"bz", "application/x-bzip"}, 98 | {"cab", "application/vnd.ms-cab-compressed"}, 99 | {"cacerts", "application/x-java-keystore"}, 100 | {"cap", "application/vnd.tcpdump.pcap"}, 101 | {"cb7", "application/x-cb7"}, 102 | {"cbl", "text/x-cobol"}, 103 | {"cbr", "application/x-cbr"}, 104 | {"cbt", "application/x-cbt"}, 105 | {"cbz", "application/vnd.comicbook+zip"}, 106 | {"ccmx", "application/x-ccmx"}, 107 | {"cc", "text/x-c++src"}, 108 | {"cda", "application/x-cda"}, 109 | {"cdf", "application/x-netcdf"}, 110 | {"cdr", "application/vnd.corel-draw"}, 111 | {"cer", "application/pkix-cert"}, 112 | {"cert", "application/x-x509-ca-cert"}, 113 | {"cgb", "application/x-gameboy-color-rom"}, 114 | {"cgm", "image/cgm"}, 115 | {"chm", "application/vnd.ms-htmlhelp"}, 116 | {"chrt", "application/x-kchart"}, 117 | {"class", "application/x-java"}, 118 | {"clpi", "video/mp2t"}, 119 | {"cls", "text/x-tex"}, 120 | {"cl", "text/x-opencl-src"}, 121 | {"cmake", "text/x-cmake"}, 122 | {"cob", "text/x-cobol"}, 123 | {"coffee", "application/vnd.coffeescript"}, 124 | {"com", "application/x-ms-dos-executable"}, 125 | {"core", "application/x-core"}, 126 | {"course", "application/x-kourse"}, 127 | {"cpio", "application/x-cpio"}, 128 | {"cpi", "video/mp2t"}, 129 | {"cpp", "text/x-c++src"}, 130 | {"cr2", "image/x-canon-cr2"}, 131 | {"crdownload", "application/x-partial-download"}, 132 | {"crl", "application/pkix-crl"}, 133 | {"crt", "application/x-x509-ca-cert"}, 134 | {"crw", "image/x-canon-crw"}, 135 | {"cs1", "image/x-kde-raw"}, 136 | {"cs2", "image/x-kde-raw"}, 137 | {"csh", "application/x-csh"}, 138 | {"csr", "application/pkcs10"}, 139 | {"css", "text/css"}, 140 | {"cs", "text/x-csharp"}, 141 | {"csvs", "text/csv-schema"}, 142 | {"csv", "text/csv"}, 143 | {"c", "text/x-csrc"}, 144 | {"c++", "text/x-c++src"}, 145 | {"cue", "application/x-cue"}, 146 | {"cur", "image/x-win-bitmap"}, 147 | {"cxx", "text/x-c++src"}, 148 | {"dar", "application/x-dar"}, 149 | {"dbf", "application/x-dbf"}, 150 | {"dbk", "application/x-docbook"}, 151 | {"dc", "application/x-dc-rom"}, 152 | {"dcl", "text/x-dcl"}, 153 | {"dcm", "application/dicom"}, 154 | {"dcr", "image/x-kodak-dcr"}, 155 | {"dds", "image/x-dds"}, 156 | {"deb", "application/vnd.debian.binary-package"}, 157 | {"der", "application/x-x509-ca-cert"}, 158 | {"desktop", "application/x-desktop"}, 159 | {"device", "text/x-systemd-unit"}, 160 | {"dia", "application/x-dia-diagram"}, 161 | {"dib", "image/bmp"}, 162 | {"dicomdir", "application/dicom"}, 163 | {"diff", "text/x-patch"}, 164 | {"directory", "application/x-desktop"}, 165 | {"di", "text/x-dsrc"}, 166 | {"divx", "video/x-msvideo"}, 167 | {"djv", "image/vnd.djvu"}, 168 | {"djvu", "image/vnd.djvu"}, 169 | {"dll", "application/x-sharedlib"}, 170 | {"dmg", "application/x-apple-diskimage"}, 171 | {"dmp", "application/vnd.tcpdump.pcap"}, 172 | {"dng", "image/x-adobe-dng"}, 173 | {"doc", "application/vnd.ms-word"}, 174 | {"docbook", "application/x-docbook"}, 175 | {"docm", "application/vnd.ms-word.document.macroenabled.12"}, 176 | {"docx", "application/vnd.opeformats-officedocument.wordprocessingml.document"}, 177 | {"dot", "application/msword-template"}, 178 | {"dotm", "application/vnd.ms-word.template.macroenabled.12"}, 179 | {"dotx", "application/vnd.opeformats-officedocument.wordprocessingml.template"}, 180 | {"dsl", "text/x-dsl"}, 181 | {"dtd", "application-dtd"}, 182 | {"d", "text/x-dsrc"}, 183 | {"dts", "audio/vnd.dts"}, 184 | {"dtshd", "audio/vnd.dts.hd"}, 185 | {"dtx", "text/x-tex"}, 186 | {"dvi", "application/x-dvi"}, 187 | {"dv", "video/dv"}, 188 | {"dwg", "image/vnd.dwg"}, 189 | {"dxf", "image/vnd.dxf"}, 190 | {"egon", "application/x-egon"}, 191 | {"eif", "text/x-eiffel"}, 192 | {"el", "text/x-emacs-lisp"}, 193 | {"emf", "image/emf"}, 194 | {"emp", "application/vnd.emusic-emusic_package"}, 195 | {"enc", "text/x-uuencode"}, 196 | {"ent", "application-external-parsed-entity"}, 197 | {"epsf", "image/x-eps"}, 198 | {"epsi", "image/x-eps"}, 199 | {"eps", "image/x-eps"}, 200 | {"epub", "application/epub+zip"}, 201 | {"erf", "application/x-endace-erf"}, 202 | {"erl", "text/x-erlang"}, 203 | {"es", "application/ecmascript"}, 204 | {"e", "text/x-eiffel"}, 205 | {"etheme", "application/x-e-theme"}, 206 | {"etx", "text/x-setext"}, 207 | {"exe", "application/x-ms-dos-executable"}, 208 | {"exr", "image/x-exr"}, 209 | {"ez", "application/andrew-inset"}, 210 | {"f4a", "audio/mp4"}, 211 | {"f4b", "audio/x-m4b"}, 212 | {"f4v", "video/mp4"}, 213 | {"f90", "text/x-fortran"}, 214 | {"f95", "text/x-fortran"}, 215 | {"fb2", "application/x-fictionbook"}, 216 | {"fds", "application/x-fds-disk"}, 217 | {"feature", "text/x-gherkin"}, 218 | {"fff", "image/x-kde-raw"}, 219 | {"fgeo", "application/x-drgeo"}, 220 | {"fig", "image/x-xfig"}, 221 | {"fits", "image/fits"}, 222 | {"fkt", "application/x-kmplot"}, 223 | {"flac", "audio/flac"}, 224 | {"fl", "application/x-fluid"}, 225 | {"flatpak", "application/vnd.flatpak"}, 226 | {"flatpakref", "application/vnd.flatpak.ref"}, 227 | {"flatpakrepo", "application/vnd.flatpak.repo"}, 228 | {"flc", "video/x-flic"}, 229 | {"fli", "video/x-flic"}, 230 | {"flv", "video/x-flv"}, 231 | {"flw", "application/x-kivio"}, 232 | {"fm", "application/vnd.framemaker"}, 233 | {"fodg", "application/vnd.oasis.opendocument.graphics-flat"}, 234 | {"fodp", "application/vnd.oasis.opendocument.presentation-flat"}, 235 | {"fods", "application/vnd.oasis.opendocument.spreadsheet-flat"}, 236 | {"fodt", "application/vnd.oasis.opendocument.text-flat"}, 237 | {"for", "text/x-fortran"}, 238 | {"fo", "text/x-xslfo"}, 239 | {"fskin", "application/x-fskin"}, 240 | {"f", "text/x-fortran"}, 241 | {"fxm", "video/x-javafx"}, 242 | {"g3", "image/fax-g3"}, 243 | {"gba", "application/x-gba-rom"}, 244 | {"gb", "application/x-gameboy-rom"}, 245 | {"gbc", "application/x-gameboy-color-rom"}, 246 | {"gbr", "image/x-gimp-gbr"}, 247 | {"gcode", "text/x.gcode"}, 248 | {"gcrd", "text/vcard"}, 249 | {"ged", "application/x-gedcom"}, 250 | {"gedcom", "application/x-gedcom"}, 251 | {"gem", "application/x-tar"}, 252 | {"gen", "application/x-genesis-rom"}, 253 | {"geojson", "application/geo+json"}, 254 | {"gf", "application/x-tex-gf"}, 255 | {"gg", "application/x-gamegear-rom"}, 256 | {"gif", "image/gif"}, 257 | {"gih", "image/x-gimp-gih"}, 258 | {"glade", "application/x-glade"}, 259 | {"gml", "application/gml"}, 260 | {"gmo", "application/x-gettext-translation"}, 261 | {"gnc", "application/x-gnucash"}, 262 | {"gnd", "application/gnunet-directory"}, 263 | {"gnucash", "application/x-gnucash"}, 264 | {"gnumeric", "application/x-gnumeric"}, 265 | {"gnuplot", "application/x-gnuplot"}, 266 | {"go", "text/x-go"}, 267 | {"gp", "application/x-gnuplot"}, 268 | {"gpg", "application/pgp-encrypted"}, 269 | {"gplt", "application/x-gnuplot"}, 270 | {"gpx", "application/gpx"}, 271 | {"gra", "application/x-graphite"}, 272 | {"grc", "application/gnuradio-grc"}, 273 | {"gsf", "application/x-font-type1"}, 274 | {"gsm", "audio/x-gsm"}, 275 | {"gs", "text/x-genie"}, 276 | {"gtar", "application/x-tar"}, 277 | {"gvp", "text/x-google-video-pointer"}, 278 | {"gv", "text/vnd.graphviz"}, 279 | {"gz", "application/gzip"}, 280 | {"h4", "application/x-hdf"}, 281 | {"h5", "application/x-hdf"}, 282 | {"hdf4", "application/x-hdf"}, 283 | {"hdf5", "application/x-hdf"}, 284 | {"hdf", "application/x-hdf"}, 285 | {"hdr", "image/x-hdr"}, 286 | {"hex", "text/x-hex"}, 287 | {"hfe", "application/x-hfe-floppy-image"}, 288 | {"hh", "text/x-c++hdr"}, 289 | {"hlp", "application/winhlp"}, 290 | {"hpgl", "application/vnd.hp-hpgl"}, 291 | {"hpp", "text/x-c++hdr"}, 292 | {"hp", "text/x-c++hdr"}, 293 | {"hrd", "image/x-kde-raw"}, 294 | {"hs", "text/x-haskell"}, 295 | {"h", "text/x-chdr"}, 296 | {"h++", "text/x-c++hdr"}, 297 | {"html", "text/html"}, 298 | {"htm", "text/html"}, 299 | {"hwp", "application/x-hwp"}, 300 | {"hwt", "application/x-hwt"}, 301 | {"hxx", "text/x-c++hdr"}, 302 | {"ia", "application/vnd.sun.writer.global"}, 303 | {"ica", "application/x-ica"}, 304 | {"icb", "image/x-tga"}, 305 | {"icc", "application/vnd.iccprofile"}, 306 | {"icm", "application/vnd.iccprofile"}, 307 | {"icns", "image/x-icns"}, 308 | {"ico", "image/vnd.microsoft.icon"}, 309 | {"icq", "application/x-icq"}, 310 | {"ics", "text/calendar"}, 311 | {"idl", "text/x-idl"}, 312 | {"ief", "image/ief"}, 313 | {"iff", "image/x-ilbm"}, 314 | {"ihex", "text/x-ihex"}, 315 | {"ilbm", "image/x-ilbm"}, 316 | {"ime", "text/x-imelody"}, 317 | {"img", "application/x-raw-disk-image"}, 318 | {"imy", "text/x-imelody"}, 319 | {"inf", "text/plain"}, 320 | {"ini", "text/plain"}, 321 | {"ins", "text/x-tex"}, 322 | {"ipfix", "application/ipfix"}, 323 | {"iptables", "text/x-iptables"}, 324 | {"ipynb", "application/x-ipynb+json"}, 325 | {"iso", "application/x-cd-image"}, 326 | {"it87", "application/x-it87"}, 327 | {"it", "audio/x-it"}, 328 | {"j2c", "image/x-jp2-codestream"}, 329 | {"j2k", "image/x-jp2-codestream"}, 330 | {"jad", "text/vnd.sun.j2me.app-descriptor"}, 331 | {"jar", "application/x-java-archive"}, 332 | {"java", "text/x-java"}, 333 | {"jceks", "application/x-java-jce-keystore"}, 334 | {"jks", "application/x-java-keystore"}, 335 | {"jng", "image/x-jng"}, 336 | {"jnlp", "application/x-java-jnlp-file"}, 337 | {"jp2", "image/jp2"}, 338 | {"jpc", "image/x-jp2-codestream"}, 339 | {"jpeg", "image/jpeg"}, 340 | {"jpe", "image/jpeg"}, 341 | {"jpf", "image/jpx"}, 342 | {"jpg2", "image/jp2"}, 343 | {"jpg", "image/jpeg"}, 344 | {"jpgm", "image/jpm"}, 345 | {"jpm", "image/jpm"}, 346 | {"jpr", "application/x-jbuilder-project"}, 347 | {"jpx", "image/jpx"}, 348 | {"jrd", "application/jrd+json"}, 349 | {"js", "application/javascript"}, 350 | {"jsm", "application/javascript"}, 351 | {"json", "application/json"}, 352 | {"jsonld", "application/ld+json"}, 353 | {"json-patch", "application/json-patch+json"}, 354 | {"k25", "image/x-kodak-k25"}, 355 | {"k3b", "application/x-k3b"}, 356 | {"k7", "application/x-thomson-cassette"}, 357 | {"kar", "audio/midi"}, 358 | {"karbon", "application/x-karbon"}, 359 | {"katefl", "text/x-katefilelist"}, 360 | {"kcfg", "application/vnd.kde.kcfg"}, 361 | {"kcfgc", "application/vnd.kde.kcfgc"}, 362 | {"kcsrc", "application/x-kcsrc"}, 363 | {"kdbx", "application/x-keepass2"}, 364 | {"kdc", "image/x-kodak-kdc"}, 365 | {"kdelnk", "application/x-desktop"}, 366 | {"kdenlive", "application/x-kdenlive"}, 367 | {"kdenlivetitle", "application/x-kdenlivetitle"}, 368 | {"kexi", "application/x-kexiproject-sqlite2"}, 369 | {"kexic", "application/x-kexi-connectiondata"}, 370 | {"kexis", "application/x-kexiproject-shortcut"}, 371 | {"key", "application/x-iwork-keynote-sffkey"}, 372 | {"kfo", "application/x-kformula"}, 373 | {"kgeo", "application/x-kgeo"}, 374 | {"kgt", "application/x-kgetlist"}, 375 | {"kig", "application/x-kig"}, 376 | {"kigz", "application/x-kig"}, 377 | {"kil", "application/x-killustrator"}, 378 | {"kim", "application/vnd.kde.kphotoalbum-import"}, 379 | {"kino", "application/smil"}, 380 | {"kmdr", "application/x-kommander"}, 381 | {"kml", "application/vnd.google-earth.kml"}, 382 | {"kmz", "application/vnd.google-earth.kmz"}, 383 | {"kns", "application/x-kns"}, 384 | {"kolf", "application/x-kourse"}, 385 | {"kolfgame", "application/x-kolf"}, 386 | {"kon", "application/x-kontour"}, 387 | {"kopete-emoticons", "application/x-kopete-emoticons"}, 388 | {"kourse", "application/x-kourse"}, 389 | {"kplato", "application/x-vnd.kde.kplato"}, 390 | {"kplatowork", "application/x-vnd.kde.kplato.work"}, 391 | {"kpm", "application/x-kpovmodeler"}, 392 | {"kpr", "application/x-kpresenter"}, 393 | {"kpt", "application/x-kpresenter"}, 394 | {"kra", "application/x-krita"}, 395 | {"ks", "application/x-java-keystore"}, 396 | {"ksp", "application/x-kspread"}, 397 | {"kth", "application/x-ktheme"}, 398 | {"ktx", "image/ktx"}, 399 | {"kud", "application/x-kugar"}, 400 | {"kug", "application/x-vnd.kde.kugar.mixed"}, 401 | {"kut", "application/x-kudesigner"}, 402 | {"kvtml", "application/x-kvtml"}, 403 | {"kwd", "application/x-kword"}, 404 | {"kwl", "application/x-kwallet"}, 405 | {"kwt", "application/x-kword"}, 406 | {"la", "application/x-shared-library-la"}, 407 | {"latex", "text/x-tex"}, 408 | {"lbm", "image/x-ilbm"}, 409 | {"ldif", "text/x-ldif"}, 410 | {"lha", "application/x-lha"}, 411 | {"lhs", "text/x-literate-haskell"}, 412 | {"lhz", "application/x-lhz"}, 413 | {"lnk", "application/x-ms-shortcut"}, 414 | {"lnx", "application/x-atari-lynx-rom"}, 415 | {"log", "text/x-log"}, 416 | {"lrv", "video/mp4"}, 417 | {"lrz", "application/x-lrzip"}, 418 | {"ltx", "text/x-tex"}, 419 | {"lua", "text/x-lua"}, 420 | {"lwob", "image/x-lwo"}, 421 | {"lwo", "image/x-lwo"}, 422 | {"lwp", "application/vnd.lotus-wordpro"}, 423 | {"lws", "image/x-lws"}, 424 | {"ly", "text/x-lilypond"}, 425 | {"lyx", "application/x-lyx"}, 426 | {"lz4", "application/x-lz4"}, 427 | {"lz", "application/x-lzip"}, 428 | {"lzh", "application/x-lha"}, 429 | {"lzma", "application/x-lzma"}, 430 | {"lzo", "application/x-lzop"}, 431 | {"m15", "audio/x-mod"}, 432 | {"m1u", "video/vnd.mpegurl"}, 433 | {"m2ts", "video/mp2t"}, 434 | {"m2t", "video/mp2t"}, 435 | {"m3u8", "audio/x-mpegurl"}, 436 | {"m3u", "audio/x-mpegurl"}, 437 | {"m4a", "audio/mp4"}, 438 | {"m4", "application/x-m4"}, 439 | {"m4b", "audio/x-m4b"}, 440 | {"m4u", "video/vnd.mpegurl"}, 441 | {"m4v", "video/mp4"}, 442 | {"m7", "application/x-thomson-cartridge-memo7"}, 443 | {"mab", "application/x-markaby"}, 444 | {"makefile", "text/x-makefile"}, 445 | {"mak", "text/x-makefile"}, 446 | {"man", "application/x-troff-man"}, 447 | {"manifest", "text/cache-manifest"}, 448 | {"markdown", "text/markdown"}, 449 | {"mbox", "application/mbox"}, 450 | {"mdb", "application/vnd.ms-access"}, 451 | {"mdc", "image/x-kde-raw"}, 452 | {"mdi", "image/vnd.ms-modi"}, 453 | {"md", "text/markdown"}, 454 | {"mdx", "application/x-genesis-32x-rom"}, 455 | {"med", "audio/x-mod"}, 456 | {"meta4", "application/metalink4"}, 457 | {"metalink", "application/metalink"}, 458 | {"me", "text/x-troff-me"}, 459 | {"mgp", "application/x-magicpoint"}, 460 | {"mht", "application/x-mimearchive"}, 461 | {"mhtml", "application/x-mimearchive"}, 462 | {"mid", "audio/midi"}, 463 | {"midi", "audio/midi"}, 464 | {"mif", "application/x-mif"}, 465 | {"minipsf", "audio/x-minipsf"}, 466 | {"mj2", "video/mj2"}, 467 | {"mjp2", "video/mj2"}, 468 | {"mjs", "application/javascript"}, 469 | {"mk3d", "video/x-matroska-3d"}, 470 | {"mka", "audio/x-matroska"}, 471 | {"mkd", "text/markdown"}, 472 | {"mk", "text/x-makefile"}, 473 | {"mkv", "video/x-matroska"}, 474 | {"mli", "text/x-ocaml"}, 475 | {"ml", "text/x-ocaml"}, 476 | {"mlt", "video/mlt-playlist"}, 477 | {"mmf", "application/x-smaf"}, 478 | {"mml", "application/mathml"}, 479 | {"mmp", "application/x-lmms-project"}, 480 | {"mmpz", "application/x-lmms-project"}, 481 | {"mm", "text/x-troff-mm"}, 482 | {"mng", "video/x-mng"}, 483 | {"mo3", "audio/x-mo3"}, 484 | {"mo", "application/x-gettext-translation"}, 485 | {"mobi", "application/x-mobipocket-ebook"}, 486 | {"moc", "text/x-moc"}, 487 | {"mod", "audio/x-mod"}, 488 | {"mof", "text/x-mof"}, 489 | {"moov", "video/quicktime"}, 490 | {"mos", "image/x-kde-raw"}, 491 | {"mo", "text/x-modelica"}, 492 | {"mount", "text/x-systemd-unit"}, 493 | {"movie", "video/x-sgi-movie"}, 494 | {"mov", "video/quicktime"}, 495 | {"mp2", "audio/mp2"}, 496 | {"mp2", "video/mpeg"}, 497 | {"mp3", "audio/mpeg"}, 498 | {"mp4", "video/mp4"}, 499 | {"mp+", "audio/x-musepack"}, 500 | {"mpc", "audio/x-musepack"}, 501 | {"mpeg", "video/mpeg"}, 502 | {"mpe", "video/mpeg"}, 503 | {"mpga", "audio/mpeg"}, 504 | {"mpg", "video/mpeg"}, 505 | {"mplog", "application/x-micropross-mplog"}, 506 | {"mpls", "video/mp2t"}, 507 | {"mpl", "video/mp2t"}, 508 | {"mpp", "audio/x-musepack"}, 509 | {"mps", "application/x-mps"}, 510 | {"mrl", "text/x-mrml"}, 511 | {"mrml", "text/x-mrml"}, 512 | {"mrw", "image/x-minolta-mrw"}, 513 | {"msi", "application/x-ms-win-installer"}, 514 | {"msod", "image/x-msod"}, 515 | {"ms", "text/x-troff-ms"}, 516 | {"msx", "application/x-msx-rom"}, 517 | {"m", "text/x-matlab"}, 518 | {"mtm", "audio/x-mod"}, 519 | {"mts", "video/mp2t"}, 520 | {"mup", "text/x-mup"}, 521 | {"mxf", "application/mxf"}, 522 | {"mxu", "video/vnd.mpegurl"}, 523 | {"n64", "application/x-n64-rom"}, 524 | {"nb", "application/mathematica"}, 525 | {"nc", "application/x-netcdf"}, 526 | {"nds", "application/x-nintendo-ds-rom"}, 527 | {"nef", "image/x-nikon-nef"}, 528 | {"nes", "application/x-nes-rom"}, 529 | {"nez", "application/x-nes-rom"}, 530 | {"nfo", "text/x-nfo"}, 531 | {"ngp", "application/x-neo-geo-pocket-rom"}, 532 | {"notifyrc", "application/vnd.kde.knotificationrc"}, 533 | {"not", "text/x-mup"}, 534 | {"nsc", "application/x-netshow-channel"}, 535 | {"nsv", "video/x-nsv"}, 536 | {"ntar", "application/x-pcapng"}, 537 | {"nzb", "application/x-nzb"}, 538 | {"o", "application/x-object"}, 539 | {"obj", "application/x-tgif"}, 540 | {"obt", "application/x-openbox-theme"}, 541 | {"ocl", "text/x-ocl"}, 542 | {"oda", "application/oda"}, 543 | {"odb", "application/vnd.oasis.opendocument.database"}, 544 | {"odc", "application/vnd.oasis.opendocument.chart"}, 545 | {"odf", "application/vnd.oasis.opendocument.formula"}, 546 | {"odg", "application/vnd.oasis.opendocument.graphics"}, 547 | {"odi", "application/vnd.oasis.opendocument.image"}, 548 | {"odm", "application/vnd.oasis.opendocument.text-master"}, 549 | {"odp", "application/vnd.oasis.opendocument.presentation"}, 550 | {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, 551 | {"odt", "application/vnd.oasis.opendocument.text"}, 552 | {"oga", "audio/ogg"}, 553 | {"ogg", "audio/ogg"}, 554 | {"ogv", "video/ogg"}, 555 | {"ogx", "application/ogg"}, 556 | {"okular", "application/vnd.kde.okular-archive"}, 557 | {"old", "application/x-trash"}, 558 | {"oleo", "application/x-oleo"}, 559 | {"ooc", "text/x-ooc"}, 560 | {"opml", "text/x-opml"}, 561 | {"oprc", "application/vnd.palm"}, 562 | {"opus", "audio/x-opus+ogg"}, 563 | {"ora", "image/openraster"}, 564 | {"orf", "image/x-olympus-orf"}, 565 | {"osd", "application/x-vnd.kde.okteta.structure"}, 566 | {"otc", "application/vnd.oasis.opendocument.chart-template"}, 567 | {"otf", "font/otf"}, 568 | {"otg", "application/vnd.oasis.opendocument.graphics-template"}, 569 | {"oth", "application/vnd.oasis.opendocument.text-web"}, 570 | {"otm", "application/vnd.oasis.opendocument.text-master-template"}, 571 | {"otp", "application/vnd.oasis.opendocument.presentation-template"}, 572 | {"ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, 573 | {"ott", "application/vnd.oasis.opendocument.text-template"}, 574 | {"owl", "application/rdf"}, 575 | {"owx", "application/owl"}, 576 | {"oxps", "application/oxps"}, 577 | {"oxt", "application/vnd.openofficeorg.extension"}, 578 | {"p10", "application/pkcs10"}, 579 | {"p12", "application/pkcs12"}, 580 | {"p65", "application/x-pagemaker"}, 581 | {"p7b", "application/pkcs7-mime"}, 582 | {"p7b", "application/x-pkcs7-certificates"}, 583 | {"p7c", "application/pkcs7-mime"}, 584 | {"p7m", "application/pkcs7-mime"}, 585 | {"p7s", "application/pkcs7-signature"}, 586 | {"p8", "application/pkcs8"}, 587 | {"pack", "application/x-java-pack200"}, 588 | {"pak", "application/x-pak"}, 589 | {"par2", "application/x-par2"}, 590 | {"part", "application/x-partial-download"}, 591 | {"pas", "text/x-pascal"}, 592 | {"patch", "text/x-patch"}, 593 | {"path", "text/x-systemd-unit"}, 594 | {"pat", "image/x-gimp-pat"}, 595 | {"pbm", "image/x-portable-bitmap"}, 596 | {"pcap", "application/vnd.tcpdump.pcap"}, 597 | {"pcapng", "application/x-pcapng"}, 598 | {"pcd", "image/x-photo-cd"}, 599 | {"pce", "application/x-pc-engine-rom"}, 600 | {"pcf", "application/x-font-pcf"}, 601 | {"pcl", "application/vnd.hp-pcl"}, 602 | {"pct", "image/x-pict"}, 603 | {"pcx", "image/vnd.zbrush.pcx"}, 604 | {"pdb", "application/vnd.palm"}, 605 | {"pdc", "application/x-aportisdoc"}, 606 | {"pdf", "application/pdf"}, 607 | {"pef", "image/x-pentax-pef"}, 608 | {"pem", "application/x-x509-ca-cert"}, 609 | {"perl", "application/x-perl"}, 610 | {"pfa", "application/x-font-type1"}, 611 | {"pfb", "application/x-font-type1"}, 612 | {"pfx", "application/pkcs12"}, 613 | {"pgm", "image/x-portable-graymap"}, 614 | {"pgn", "application/vnd.chess-pgn"}, 615 | {"pgp", "application/pgp-encrypted"}, 616 | {"php3", "application/x-php"}, 617 | {"php4", "application/x-php"}, 618 | {"php5", "application/x-php"}, 619 | {"php", "application/x-php"}, 620 | {"phps", "application/x-php"}, 621 | {"pic", "image/x-pic"}, 622 | {"pict1", "image/x-pict"}, 623 | {"pict2", "image/x-pict"}, 624 | {"pict", "image/x-pict"}, 625 | {"pk", "application/x-tex-pk"}, 626 | {"pkg", "application/x-xar"}, 627 | {"pkipath", "application/pkix-pkipath"}, 628 | {"pklg", "application/x-apple-packetlogger"}, 629 | {"pkpass", "application/vnd.apple.pkpass"}, 630 | {"pkr", "application/pgp-keys"}, 631 | {"pkt", "application/x-etherpeek"}, 632 | {"pla", "audio/x-iriver-pla"}, 633 | {"plan", "application/x-vnd.kde.plan"}, 634 | {"planwork", "application/x-vnd.kde.plan.work"}, 635 | {"pl", "application/x-perl"}, 636 | {"PL", "application/x-perl"}, 637 | {"plasmoid", "application/x-plasma"}, 638 | {"pln", "application/x-planperfect"}, 639 | {"pls", "audio/x-scpls"}, 640 | {"pm6", "application/x-pagemaker"}, 641 | {"pm", "application/x-pagemaker"}, 642 | {"pm", "application/x-perl"}, 643 | {"pmd", "application/x-pagemaker"}, 644 | {"png", "image/png"}, 645 | {"pnm", "image/x-portable-anymap"}, 646 | {"pntg", "image/x-macpaint"}, 647 | {"pnx", "image/x-kde-raw"}, 648 | {"pod", "application/x-perl"}, 649 | {"por", "application/x-spss-por"}, 650 | {"pot", "application/vnd.ms-powerpoint"}, 651 | {"po", "text/x-gettext-translation"}, 652 | {"potm", "application/vnd.ms-powerpoint.template.macroenabled.12"}, 653 | {"pot", "text/x-gettext-translation-template"}, 654 | {"potx", "application/vnd.opeformats-officedocument.presentationml.template"}, 655 | {"ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"}, 656 | {"ppm", "image/x-portable-pixmap"}, 657 | {"pps", "application/vnd.ms-powerpoint"}, 658 | {"ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"}, 659 | {"ppsx", "application/vnd.opeformats-officedocument.presentationml.slideshow"}, 660 | {"ppt", "application/vnd.ms-powerpoint"}, 661 | {"pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"}, 662 | {"pptx", "application/vnd.opeformats-officedocument.presentationml.presentation"}, 663 | {"ppz", "application/vnd.ms-powerpoint"}, 664 | {"pqa", "application/vnd.palm"}, 665 | {"prc", "application/x-mobipocket-ebook"}, 666 | {"pro", "application/vnd.nokia.qt.qmakeprofile"}, 667 | {"ps", "application/postscript"}, 668 | {"psd", "image/vnd.adobe.photoshop"}, 669 | {"psf", "audio/x-psf"}, 670 | {"psflib", "audio/x-psflib"}, 671 | {"psid", "audio/prs.sid"}, 672 | {"psw", "application/x-pocket-word"}, 673 | {"pt", "application/vnd.oasis.opendocument.text-master"}, 674 | {"pt", "application/vnd.oasis.opendocument.text-master-template"}, 675 | {"pt", "application/vnd.sun.writer.global"}, 676 | {"p", "text/x-pascal"}, 677 | {"pub", "application/vnd.ms-publisher"}, 678 | {"pw", "application/x-pw"}, 679 | {"pyc", "application/x-python-bytecode"}, 680 | {"pyo", "application/x-python-bytecode"}, 681 | {"qif", "application/x-qw"}, 682 | {"qif", "image/x-quicktime"}, 683 | {"qmlproject", "text/x-qml"}, 684 | {"qml", "text/x-qml"}, 685 | {"qmltypes", "text/x-qml"}, 686 | {"qp", "application/x-qpress"}, 687 | {"qrc", "application/vnd.nokia.qt.resource"}, 688 | {"qti", "application/x-qtiplot"}, 689 | {"qtif", "image/x-quicktime"}, 690 | {"qtl", "application/x-quicktime-media-link"}, 691 | {"qt", "video/quicktime"}, 692 | {"qtvr", "video/quicktime"}, 693 | {"quanta", "application/x-quanta"}, 694 | {"ra", "audio/vnd.rn-realaudio"}, 695 | {"raf", "image/x-fuji-raf"}, 696 | {"ram", "application/ram"}, 697 | {"raml", "application/raml+yaml"}, 698 | {"rar", "application/vnd.rar"}, 699 | {"ras", "image/x-cmu-raster"}, 700 | {"raw", "image/x-panasonic-rw"}, 701 | {"rax", "audio/vnd.rn-realaudio"}, 702 | {"rb", "application/x-ruby"}, 703 | {"rc", "application/vnd.kde.guirc"}, 704 | {"rdc", "image/x-kde-raw"}, 705 | {"rdf", "application/rdf"}, 706 | {"rdfs", "application/rdf"}, 707 | {"rdp", "application/x-remmina"}, 708 | {"RDP", "application/x-remmina"}, 709 | {"rdpx", "application/x-remmina"}, 710 | {"reg", "text/x-ms-regedit"}, 711 | {"rej", "text/x-reject"}, 712 | {"remmina", "application/x-remmina"}, 713 | {"rf5", "application/x-tektronix-rf5"}, 714 | {"rgb", "image/x-rgb"}, 715 | {"rle", "image/rle"}, 716 | {"rm", "application/vnd.rn-realmedia"}, 717 | {"rmj", "application/vnd.rn-realmedia"}, 718 | {"rmm", "application/vnd.rn-realmedia"}, 719 | {"rms", "application/vnd.rn-realmedia"}, 720 | {"rmvb", "application/vnd.rn-realmedia"}, 721 | {"rmx", "application/vnd.rn-realmedia"}, 722 | {"rnc", "application/relax-ng-compact-syntax"}, 723 | {"rng", "application"}, 724 | {"rng", "application/relaxng"}, 725 | {"roff", "text/troff"}, 726 | {"rp", "image/vnd.rn-realpix"}, 727 | {"rpm", "application/x-rpm"}, 728 | {"rss", "application/rss"}, 729 | {"rs", "text/rust"}, 730 | {"rtf", "application/rtf"}, 731 | {"rt", "text/vnd.rn-realtext"}, 732 | {"rtx", "text/richtext"}, 733 | {"rv", "video/vnd.rn-realvideo"}, 734 | {"rvx", "video/vnd.rn-realvideo"}, 735 | {"rw2", "image/x-panasonic-rw2"}, 736 | {"s19", "text/x-srecord"}, 737 | {"s28", "text/x-srecord"}, 738 | {"s37", "text/x-srecord"}, 739 | {"s3m", "audio/x-s3m"}, 740 | {"sam", "application/x-amipro"}, 741 | {"sami", "application/x-sami"}, 742 | {"sap", "application/x-thomson-sap-image"}, 743 | {"sass", "text/x-sass"}, 744 | {"sav", "application/x-spss-sav"}, 745 | {"scala", "text/x-scala"}, 746 | {"scm", "text/x-scheme"}, 747 | {"scope", "text/x-systemd-unit"}, 748 | {"scss", "text/x-scss"}, 749 | {"sda", "application/vnd.stardivision.draw"}, 750 | {"sdc", "application/vnd.stardivision.calc"}, 751 | {"sdd", "application/vnd.stardivision.impress"}, 752 | {"sdp", "application/sdp"}, 753 | {"sdp", "application/vnd.stardivision.impress"}, 754 | {"sds", "application/vnd.stardivision.chart"}, 755 | {"sdw", "application/vnd.stardivision.writer"}, 756 | {"seg", "application/x-kseg"}, 757 | {"service", "text/x-systemd-unit"}, 758 | {"sfc", "application/vnd.nintendo.snes.rom"}, 759 | {"sg", "application/x-sg1000-rom"}, 760 | {"sgb", "application/x-gameboy-rom"}, 761 | {"sgf", "application/x-go-sgf"}, 762 | {"sgi", "image/x-sgi"}, 763 | {"sgl", "application/vnd.stardivision.writer"}, 764 | {"sgml", "text/sgml"}, 765 | {"sgm", "text/sgml"}, 766 | {"sgrd", "application/x-ksysguard"}, 767 | {"shape", "application/x-dia-shape"}, 768 | {"sh", "application/x-shellscript"}, 769 | {"shar", "application/x-shar"}, 770 | {"shn", "application/x-shorten"}, 771 | {"shriman", "application/x-gip"}, 772 | {"siag", "application/x-siag"}, 773 | {"sid", "audio/prs.sid"}, 774 | {"sig", "application/pgp-signature"}, 775 | {"sik", "application/x-trash"}, 776 | {"sis", "application/vnd.symbian.install"}, 777 | {"sit", "application/x-stuffit"}, 778 | {"siv", "application/sieve"}, 779 | {"sk1", "image/x-skencil"}, 780 | {"sk", "image/x-skencil"}, 781 | {"skr", "application/pgp-keys"}, 782 | {"skz", "application/x-superkaramba"}, 783 | {"sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"}, 784 | {"sldx", "application/vnd.opeformats-officedocument.presentationml.slide"}, 785 | {"slice", "text/x-systemd-unit"}, 786 | {"slk", "text/spreadsheet"}, 787 | {"smaf", "application/x-smaf"}, 788 | {"smc", "application/vnd.nintendo.snes.rom"}, 789 | {"smd", "application/vnd.stardivision.mail"}, 790 | {"smf", "application/vnd.stardivision.math"}, 791 | {"smi", "application/smil"}, 792 | {"smil", "application/smil"}, 793 | {"sml", "application/smil"}, 794 | {"sms", "application/x-sms-rom"}, 795 | {"snap", "application/vnd.snap"}, 796 | {"snd", "audio/basic"}, 797 | {"snf", "application/x-font-snf"}, 798 | {"snoop", "application/x-snoop"}, 799 | {"so", "application/x-sharedlib"}, 800 | {"socket", "text/x-systemd-unit"}, 801 | {"spc", "application/pkcs7-mime"}, 802 | {"spd", "application/x-font-speedo"}, 803 | {"spec", "text/x-rpm-spec"}, 804 | {"spkac", "application/x-spkac"}, 805 | {"spl", "application/vnd.adobe.flash.movie"}, 806 | {"spm", "application/x-source-rpm"}, 807 | {"spx", "audio/x-speex"}, 808 | {"sql", "application/sql"}, 809 | {"sqsh", "application/vnd.squashfs"}, 810 | {"sr2", "image/x-sony-sr2"}, 811 | {"src", "application/x-wais-source"}, 812 | {"srec", "text/x-srecord"}, 813 | {"srf", "image/x-sony-srf"}, 814 | {"srt", "application/x-subrip"}, 815 | {"ssa", "text/x-ssa"}, 816 | {"ss", "text/x-scheme"}, 817 | {"stc", "application/vnd.sun.calc.template"}, 818 | {"std", "application/vnd.sun.draw.template"}, 819 | {"sti", "application/vnd.sun.impress.template"}, 820 | {"stm", "audio/x-stm"}, 821 | {"stw", "application/vnd.sun.writer.template"}, 822 | {"sty", "text/x-tex"}, 823 | {"sub", "text/x-mpsub"}, 824 | {"sun", "image/x-sun-raster"}, 825 | {"sv4cpio", "application/x-sv4cpio"}, 826 | {"sv4crc", "application/x-sv4crc"}, 827 | {"svg", "image/svg"}, 828 | {"svgz", "image/svg-compressed"}, 829 | {"svh", "text/x-svhdr"}, 830 | {"sv", "text/x-svsrc"}, 831 | {"swap", "text/x-systemd-unit"}, 832 | {"swf", "application/vnd.adobe.flash.movie"}, 833 | {"swm", "application/x-ms-wim"}, 834 | {"sxc", "application/vnd.sun.calc"}, 835 | {"sxd", "application/vnd.sun.draw"}, 836 | {"sxg", "application/vnd.sun.writer.global"}, 837 | {"sxi", "application/vnd.sun.impress"}, 838 | {"sxm", "application/vnd.sun.math"}, 839 | {"sxw", "application/vnd.sun.writer"}, 840 | {"sylk", "text/spreadsheet"}, 841 | {"t2t", "text/x-txt2tags"}, 842 | {"tar", "application/x-tar"}, 843 | {"taz", "application/x-tarz"}, 844 | {"tb2", "application/x-bzip-compressed-tar"}, 845 | {"tbz2", "application/x-bzip-compressed-tar"}, 846 | {"tbz", "application/x-bzip-compressed-tar"}, 847 | {"tcl", "text/x-tcl"}, 848 | {"texinfo", "text/x-texinfo"}, 849 | {"texi", "text/x-texinfo"}, 850 | {"tex", "text/x-tex"}, 851 | {"tga", "image/x-tga"}, 852 | {"tgz", "application/x-compressed-tar"}, 853 | {"theme", "application/x-theme"}, 854 | {"themepack", "application/x-windows-themepack"}, 855 | {"tiff", "image/tiff"}, 856 | {"tif", "image/tiff"}, 857 | {"timer", "text/x-systemd-unit"}, 858 | {"tk", "text/x-tcl"}, 859 | {"tlrz", "application/x-lrzip-compressed-tar"}, 860 | {"tlz", "application/x-lzma-compressed-tar"}, 861 | {"tnef", "application/vnd.ms-tnef"}, 862 | {"tnf", "application/vnd.ms-tnef"}, 863 | {"toc", "application/x-cdrdao-toc"}, 864 | {"torrent", "application/x-bittorrent"}, 865 | {"tpc", "application/x-etherpeek"}, 866 | {"tpic", "image/x-tga"}, 867 | {"tr1", "application/x-lanalyzer"}, 868 | {"trc0", "application/x-nettl"}, 869 | {"trc1", "application/x-nettl"}, 870 | {"trig", "application/trig"}, 871 | {"tr", "text/troff"}, 872 | {"ts", "video/mp2t"}, 873 | {"tsv", "text/tab-separated-values"}, 874 | {"tta", "audio/x-tta"}, 875 | {"ttc", "font/collection"}, 876 | {"ttf", "font/ttf"}, 877 | {"ttl", "text/turtle"}, 878 | {"ttx", "application/x-font-ttx"}, 879 | {"tuberling", "application/x-tuberling"}, 880 | {"twig", "text/x-twig"}, 881 | {"txt", "text/plain"}, 882 | {"txz", "application/x-xz-compressed-tar"}, 883 | {"tzo", "application/x-tzo"}, 884 | {"udeb", "application/vnd.debian.binary-package"}, 885 | {"ufraw", "application/x-ufraw"}, 886 | {"ui", "application/x-designer"}, 887 | {"ui", "application/x-gtk-builder"}, 888 | {"uil", "text/x-uil"}, 889 | {"uin", "application/x-icq"}, 890 | {"ult", "audio/x-mod"}, 891 | {"unf", "application/x-nes-rom"}, 892 | {"uni", "audio/x-mod"}, 893 | {"unif", "application/x-nes-rom"}, 894 | {"url", "application/x-mswinurl"}, 895 | {"ustar", "application/x-ustar"}, 896 | {"uue", "text/x-uuencode"}, 897 | {"uu", "text/x-uuencode"}, 898 | {"v64", "application/x-n64-rom"}, 899 | {"vala", "text/x-vala"}, 900 | {"vapi", "text/x-vala"}, 901 | {"vb", "application/x-virtual-boy-rom"}, 902 | {"vbs", "application/x-vbscript"}, 903 | {"vcard", "text/vcard"}, 904 | {"vcf", "text/vcard"}, 905 | {"vcs", "text/calendar"}, 906 | {"vct", "text/vcard"}, 907 | {"vda", "image/x-tga"}, 908 | {"vhdl", "text/x-vhdl"}, 909 | {"vhd", "text/x-vhdl"}, 910 | {"vivo", "video/vnd.vivo"}, 911 | {"viv", "video/vnd.vivo"}, 912 | {"vlc", "audio/x-mpegurl"}, 913 | {"vob", "video/mpeg"}, 914 | {"voc", "audio/x-voc"}, 915 | {"vor", "application/vnd.stardivision.writer"}, 916 | {"vsd", "application/vnd.visio"}, 917 | {"vsdm", "application/vnd.ms-visio.drawing.macroenabled.main"}, 918 | {"vsdx", "application/vnd.ms-visio.drawing.main"}, 919 | {"vss", "application/vnd.visio"}, 920 | {"vssm", "application/vnd.ms-visio.stencil.macroenabled.main"}, 921 | {"vssx", "application/vnd.ms-visio.stencil.main"}, 922 | {"vst", "application/vnd.visio"}, 923 | {"vst", "image/x-tga"}, 924 | {"vstm", "application/vnd.ms-visio.template.macroenabled.main"}, 925 | {"vstx", "application/vnd.ms-visio.template.main"}, 926 | {"vsw", "application/vnd.visio"}, 927 | {"v", "text/x-verilog"}, 928 | {"vtt", "text/vtt"}, 929 | {"vv", "application/x-virt-viewer"}, 930 | {"vwr", "application/x-ixia-vwr"}, 931 | {"wad", "application/x-wii-wad"}, 932 | {"war", "application/x-webarchive"}, 933 | {"wav", "audio/x-wav"}, 934 | {"wax", "audio/x-ms-asx"}, 935 | {"wb1", "application/x-quattropro"}, 936 | {"wb2", "application/x-quattropro"}, 937 | {"wb3", "application/x-quattropro"}, 938 | {"wbmp", "image/vnd.wap.wbmp"}, 939 | {"wcm", "application/vnd.ms-works"}, 940 | {"wdb", "application/vnd.ms-works"}, 941 | {"webm", "video/webm"}, 942 | {"webp", "image/webp"}, 943 | {"westley", "video/mlt-playlist"}, 944 | {"wim", "application/x-ms-wim"}, 945 | {"wk1", "application/vnd.lotus-1-2-3"}, 946 | {"wk3", "application/vnd.lotus-1-2-3"}, 947 | {"wk4", "application/vnd.lotus-1-2-3"}, 948 | {"wkdownload", "application/x-partial-download"}, 949 | {"wks", "application/vnd.lotus-1-2-3"}, 950 | {"wks", "application/vnd.ms-works"}, 951 | {"wma", "audio/x-ms-wma"}, 952 | {"wmf", "image/wmf"}, 953 | {"wmls", "text/vnd.wap.wmlscript"}, 954 | {"wml", "text/vnd.wap.wml"}, 955 | {"wmp", "video/x-ms-wmp"}, 956 | {"wmv", "video/x-ms-wmv"}, 957 | {"wmx", "audio/x-ms-asx"}, 958 | {"woff2", "font/woff"}, 959 | {"woff", "font/woff"}, 960 | {"wp4", "application/vnd.wordperfect"}, 961 | {"wp5", "application/vnd.wordperfect"}, 962 | {"wp6", "application/vnd.wordperfect"}, 963 | {"wp", "application/vnd.wordperfect"}, 964 | {"wpd", "application/vnd.wordperfect"}, 965 | {"wpg", "application/x-wpg"}, 966 | {"wpl", "application/vnd.ms-wpl"}, 967 | {"wpp", "application/vnd.wordperfect"}, 968 | {"wps", "application/vnd.ms-works"}, 969 | {"wpz", "application/x-etherpeek"}, 970 | {"wql", "application/x-kwordquiz"}, 971 | {"wri", "application/x-mswrite"}, 972 | {"ws", "application/x-wonderswan-rom"}, 973 | {"wsc", "application/x-wonderswan-color-rom"}, 974 | {"wv", "audio/x-wavpack"}, 975 | {"wvc", "audio/x-wavpack-correction"}, 976 | {"wvp", "audio/x-wavpack"}, 977 | {"wvx", "audio/x-ms-asx"}, 978 | {"wwf", "application/x-wwf"}, 979 | {"x3f", "image/x-sigma-x3f"}, 980 | {"xac", "application/x-gnucash"}, 981 | {"xar", "application/x-xar"}, 982 | {"xbel", "application/x-xbel"}, 983 | {"xbl", "application"}, 984 | {"xbm", "image/x-xbitmap"}, 985 | {"xcf", "image/x-xcf"}, 986 | {"xdgapp", "application/vnd.flatpak"}, 987 | {"xht", "application/xhtml"}, 988 | {"xhtml", "application/xhtml"}, 989 | {"xi", "audio/x-xi"}, 990 | {"xla", "application/vnd.ms-excel"}, 991 | {"xlam", "application/vnd.ms-excel.addin.macroenabled.12"}, 992 | {"xlc", "application/vnd.ms-excel"}, 993 | {"xld", "application/vnd.ms-excel"}, 994 | {"xlf", "application/x-xliff"}, 995 | {"xliff", "application/x-xliff"}, 996 | {"xll", "application/vnd.ms-excel"}, 997 | {"xlm", "application/vnd.ms-excel"}, 998 | {"xlr", "application/vnd.ms-works"}, 999 | {"xls", "application/vnd.ms-excel"}, 1000 | {"xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"}, 1001 | {"xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"}, 1002 | {"xlsx", "application/vnd.opeformats-officedocument.spreadsheetml.sheet"}, 1003 | {"xlt", "application/vnd.ms-excel"}, 1004 | {"xltm", "application/vnd.ms-excel.template.macroenabled.12"}, 1005 | {"xltx", "application/vnd.opeformats-officedocument.spreadsheetml.template"}, 1006 | {"xlw", "application/vnd.ms-excel"}, 1007 | {"xm", "audio/x-xm"}, 1008 | {"xmf", "audio/x-xmf"}, 1009 | {"xmi", "text/x-xmi"}, 1010 | {"xml", "application"}, 1011 | {"xpi", "application/x-xpinstall"}, 1012 | {"xpm", "image/x-xpixmap"}, 1013 | {"xps", "application/oxps"}, 1014 | {"xsd", "application"}, 1015 | {"xsd", "application/xsd"}, 1016 | {"xsl", "application/xslt"}, 1017 | {"xslfo", "text/x-xslfo"}, 1018 | {"xslt", "application/xslt"}, 1019 | {"xspf", "application/xspf"}, 1020 | {"xul", "application/vnd.mozilla.xul"}, 1021 | {"xwd", "image/x-xwindowdump"}, 1022 | {"xxe", "text/x-xxencode"}, 1023 | {"xz", "application/x-xz"}, 1024 | {"yaml", "application/x-yaml"}, 1025 | {"yml", "application/x-yaml"}, 1026 | {"z64", "application/x-n64-rom"}, 1027 | {"zabw", "application/x-abiword"}, 1028 | {"z", "application/x-compress"}, 1029 | {"zip", "application/zip"}, 1030 | {"zoo", "application/x-zoo"}, 1031 | {"zsav", "application/x-spss-sav"}, 1032 | {"zz", "application/zlib"} 1033 | }; 1034 | 1035 | const std::string &Util::mime_type(std::string dot_file_ext) { 1036 | for (auto &it : dot_file_ext) { 1037 | it = tolower(it); 1038 | } 1039 | 1040 | auto it = mime_types.find(dot_file_ext); 1041 | 1042 | if (it == mime_types.end()) 1043 | return unknown_mime_type; 1044 | else 1045 | return it->second; 1046 | } --------------------------------------------------------------------------------