Select the types of activity you want to include in your feed.
feat: titan support
This commit is huge...
For the most part, this commit just adds Titan support. However, this commit also refactors the `maple/` directory so that every complex block lives in it's own namespace.
···11+/*
22+ * This file is part of Maple <https://github.com/gemrest/maple>.
33+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
44+ *
55+ * This program is free software: you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
77+ * the Free Software Foundation, version 3.
88+ *
99+ * This program is distributed in the hope that it will be useful, but
1010+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1212+ * General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ *
1717+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
1818+ * SPDX-License-Identifier: GPL-3.0-only
1919+ */
2020+2121+#include <fstream>
2222+#include <iostream>
2323+2424+#include "gemini.hh"
2525+2626+namespace maple::gemini {
2727+ auto handle_client(
2828+ std::vector<std::string> gemini_files,
2929+ std::string path,
3030+ std::stringstream &response
3131+ ) -> void {
3232+ // Check if the route is a file being served
3333+ if (std::find(
3434+ gemini_files.begin(),
3535+ gemini_files.end(),
3636+ ".maple/gmi" + path
3737+ ) != gemini_files.end()) {
3838+ // If the route is a file being served; get the file contents
3939+4040+ std::ifstream file(".maple/gmi" + path);
4141+ std::stringstream buffer;
4242+4343+ buffer << file.rdbuf();
4444+4545+ file.close();
4646+4747+ response << "20 text/gemini\r\n" << buffer.str();
4848+ } else {
4949+ if (path.empty() || path.at(path.length() - 1) == '/') {
5050+ std::ifstream file(".maple/gmi" + path + "index.gmi");
5151+ std::stringstream buffer;
5252+5353+ buffer << file.rdbuf();
5454+5555+ response << "20 text/gemini\r\n" << buffer.str();
5656+ } else {
5757+ response
5858+ << "51 The server (Maple) could not find the specified file.\r\n";
5959+ }
6060+ }
6161+6262+ std::cout << "requested " << path << std::endl;
6363+ }
6464+}
+35
maple/gemini.hh
···11+/*
22+ * This file is part of Maple <https://github.com/gemrest/maple>.
33+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
44+ *
55+ * This program is free software: you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
77+ * the Free Software Foundation, version 3.
88+ *
99+ * This program is distributed in the hope that it will be useful, but
1010+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1212+ * General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ *
1717+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
1818+ * SPDX-License-Identifier: GPL-3.0-only
1919+ */
2020+2121+#ifndef GEMINI_HH
2222+#define GEMINI_HH
2323+2424+#include <sstream>
2525+#include <vector>
2626+2727+namespace maple::gemini {
2828+ auto handle_client(
2929+ std::vector<std::string>,
3030+ std::string,
3131+ std::stringstream &
3232+ ) -> void;
3333+}
3434+3535+#endif // GEMINI_HH
+151-77
maple/maple.cc
···2121#include <arpa/inet.h>
2222#include <csignal>
2323#include <filesystem>
2424-#include <fstream>
2524#include <iostream>
2525+#include <map>
2626#include <openssl/err.h>
2727-#include <openssl/ssl.h>
2827#include <sys/socket.h>
2928#include <unistd.h>
3029#include <vector>
31303232-static int maple_socket;
3333-static SSL_CTX *ssl_context;
3434-3535-auto exit_with[[noreturn]](const char *, bool) -> void;
3131+#include "gemini.hh"
3232+#include "maple.hh"
3333+#include "titan.hh"
36343735auto main() -> int {
3836 sockaddr_in socket_address {};
3937 std::vector<std::string> gemini_files;
3838+ bool titan = false;
3939+ std::string titan_token;
4040+ size_t titan_max_size = 0;
4141+4242+ // Check if the user is want to support Titan
4343+ {
4444+ char *titan_environment = std::getenv("TITAN");
4545+4646+ if (titan_environment == nullptr) {
4747+ titan = false;
4848+ } else {
4949+ std::string valid_titan_environment(titan_environment);
5050+5151+ std::transform(
5252+ valid_titan_environment.begin(),
5353+ valid_titan_environment.end(),
5454+ valid_titan_environment.begin(),
5555+ [](unsigned char c) -> int { return std::tolower(c); }
5656+ );
5757+5858+ if (valid_titan_environment == "true" || valid_titan_environment == "1") {
5959+ char *unvalidated_titan_token = std::getenv("TITAN_TOKEN");
6060+ char *unvalidated_titan_max_size = std::getenv("TITAN_MAX_SIZE");
6161+6262+ if (unvalidated_titan_token == nullptr) {
6363+ titan_token = "";
6464+ } else {
6565+ titan_token = std::string(unvalidated_titan_token);
6666+ }
6767+6868+ if (unvalidated_titan_max_size == nullptr) {
6969+ titan_max_size = 1024;
7070+7171+ std::cout << "no TITAN_MAX_SIZE set, defaulting to 1024" << std::endl;
7272+ } else {
7373+ try {
7474+ titan_max_size = static_cast<size_t>(
7575+ std::stoi(unvalidated_titan_max_size)
7676+ );
7777+ } catch (...) {
7878+ maple::exit_with(
7979+ "TITAN_MAX_SIZE could not be interpreted as an integer",
8080+ false
8181+ );
8282+ }
8383+ }
8484+8585+ titan = true;
8686+ }
8787+ }
8888+ }
40894190 // Try a graceful shutdown when a SIGINT is detected
4291 signal(SIGINT, [](int signal_) -> void {
4392 std::cout << "shutdown(" << signal_ << ")" << std::endl;
44934545- close(maple_socket);
4646- SSL_CTX_free(ssl_context);
9494+ close(maple::maple_socket);
9595+ SSL_CTX_free(maple::ssl_context);
4796 });
48974998 // Find and keep track of all Gemini files to serve
···61110 file_extension.end(),
62111 gemini_file_extension.begin(),
63112 gemini_file_extension.end(),
6464- [](auto a, auto b) -> bool {
6565- return std::tolower(a) == std::tolower(b);
113113+ [](auto a, auto b) -> bool {return std::tolower(a) == std::tolower(b);
66114 }
67115 )) {
68116 gemini_files.push_back(entry.path());
···78126 SSL_library_init();
79127 SSL_load_error_strings();
801288181- ssl_context = SSL_CTX_new(TLS_server_method());
8282- if (!ssl_context) {
8383- exit_with("unable to create ssl context", true);
129129+ maple::ssl_context = SSL_CTX_new(TLS_server_method());
130130+ if (!maple::ssl_context) {
131131+ maple::exit_with("unable to create ssl context", true);
84132 }
8513386134 if (SSL_CTX_use_certificate_file(
8787- ssl_context,
135135+ maple::ssl_context,
88136 ".maple/public.pem",
89137 SSL_FILETYPE_PEM
90138 ) <= 0) {
9191- exit_with("unable to use certificate file", true);
139139+ maple::exit_with("unable to use certificate file", true);
92140 }
93141 if (SSL_CTX_use_PrivateKey_file(
9494- ssl_context,
142142+ maple::ssl_context,
95143 ".maple/private.pem",
96144 SSL_FILETYPE_PEM
97145 ) <= 0) {
9898- exit_with("unable to use private key file", true);
146146+ maple::exit_with("unable to use private key file", true);
99147 }
100148101149 socket_address.sin_family = AF_INET;
102150 socket_address.sin_port = htons(1965);
103151 socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
104152105105- maple_socket = socket(AF_INET, SOCK_STREAM, 0);
106106- if (maple_socket < 0) {
107107- exit_with("unable to create socket", false);
153153+ maple::maple_socket = socket(AF_INET, SOCK_STREAM, 0);
154154+155155+ if (maple::maple_socket < 0) {
156156+ maple::exit_with("unable to create socket", false);
108157 }
109158110159 // Reuse address. Allows the use of the address instantly after a SIGINT
111160 // without having to wait for the socket to die.
112161 int reuse_addr = 1;
113162 if (setsockopt(
114114- maple_socket,
163163+ maple::maple_socket,
115164 SOL_SOCKET,
116165 SO_REUSEADDR,
117166 &reuse_addr,
118167 sizeof(int)
119168 ) < 0) {
120120- exit_with("unable to set socket options (SO_LINGER)", false);
169169+ maple::exit_with("unable to set socket options (SO_LINGER)", false);
121170 }
122171123172 if (bind(
124124- maple_socket,
173173+ maple::maple_socket,
125174 reinterpret_cast<sockaddr *>(&socket_address),
126175 sizeof(socket_address)
127176 ) < 0) {
128128- exit_with("unable to bind", false);
177177+ maple::exit_with("unable to bind", false);
129178 }
130130- if (listen(maple_socket, 1) < 0) {
131131- exit_with("unable to listen", false);
179179+ if (listen(maple::maple_socket, 1) < 0) {
180180+ maple::exit_with("unable to listen", false);
132181 }
133182134183 // Listen and serve connections
···137186 unsigned int socket_address_length = sizeof(socket_address_);
138187 SSL *ssl;
139188 int client = accept(
140140- maple_socket,
189189+ maple::maple_socket,
141190 reinterpret_cast<sockaddr *>(&socket_address_),
142191 &socket_address_length
143192 );
144193 char request[1024];
145194146146- if (client < 0) { exit_with("unable to accept", false); }
195195+ if (client < 0) { maple::exit_with("unable to accept", false); }
147196148148- ssl = SSL_new(ssl_context);
197197+ ssl = SSL_new(maple::ssl_context);
149198 SSL_set_fd(ssl, client);
150199151200 if (SSL_accept(ssl) <= 0) {
···153202 } else {
154203 std::stringstream response;
155204 size_t index_of_junk;
205205+ int request_scheme; // Gemini = 1, Titan = 2, Error = 0
206206+ size_t bytes_read;
156207157157- SSL_read(ssl, request, sizeof(request));
208208+ SSL_read_ex(ssl, request, sizeof(request), &bytes_read);
158209159210 std::string path(request);
160211161161- path = path.substr(0, path.size() - 2); // Remove "\r\n"
162162- path.erase(0, 9); // Remove "gemini://"
163163-164164- // Try to remove the host, if you cannot; it must be a trailing slash-less
165165- // hostname, so we will respond with the index.
166166- size_t found_first = path.find_first_of('/');
167167- if (found_first != std::string::npos) {
168168- path = path.substr(
169169- found_first,
170170- path.size() - 1
171171- ); // Remove host
212212+ if (path.starts_with("gemini://")) {
213213+ request_scheme = 1;
214214+ } else if (path.starts_with("titan://")) {
215215+ request_scheme = 2;
172216 } else {
173173- path = "/index.gmi";
217217+ request_scheme = 0;
174218 }
175219176176- // Remove junk, if any
177177- index_of_junk = path.find_first_of('\n');
178178- if (index_of_junk != std::string::npos) {
179179- path.erase(
180180- path.find_first_of('\n') - 1,
181181- path.size() - 1
182182- );
183183- }
220220+ if (request_scheme != 0) {
221221+ path = path.substr(0, bytes_read);
184222185185- // Check if the route is a file being served
186186- if (std::find(
187187- gemini_files.begin(),
188188- gemini_files.end(),
189189- ".maple/gmi" + path
190190- ) != gemini_files.end()) {
191191- // If the route is a file being served; get the file contents
223223+ // Remove "\r\n" if Gemini
224224+ if (request_scheme == 1) {
225225+ path = path.substr(0, path.size() - 2);
226226+ }
192227193193- std::ifstream file(".maple/gmi" + path);
194194- std::stringstream buffer;
195195-196196- buffer << file.rdbuf();
228228+ if (request_scheme == 1) {
229229+ path.erase(0, 9); // Remove "gemini://"
230230+ } else {
231231+ path.erase(0, 8); // Remove "titan://"
232232+ }
197233198198- response << "20 text/gemini\r\n" << buffer.str();
199199- } else {
200200- if (path.empty() || path.at(path.length() - 1) == '/') {
201201- std::ifstream file(".maple/gmi" + path + "index.gmi");
202202- std::stringstream buffer;
234234+ // Try to remove the host, if you cannot; it must be a trailing
235235+ // slash-less hostname, so we will respond with the index.
236236+ size_t found_first = path.find_first_of('/');
237237+ if (found_first != std::string::npos) {
238238+ path = path.substr(
239239+ found_first,
240240+ path.size() - 1
241241+ ); // Remove host
242242+ } else {
243243+ path = "/index.gmi";
244244+ }
203245204204- buffer << file.rdbuf();
246246+// std::cout << "1: \"" << path << "\"" << std::endl;
205247206206- response << "20 text/gemini\r\n" << buffer.str();
207207- } else {
208208- response << "51 The server (Maple) could not find the specified file.\r\n";
248248+ if (request_scheme == 1) {
249249+ // Remove junk, if any
250250+ index_of_junk = path.find_first_of('\n');
251251+ if (index_of_junk != std::string::npos) {
252252+ path.erase(
253253+ path.find_first_of('\n') - 1,
254254+ path.size() - 1
255255+ );
256256+ }
209257 }
210210- }
258258+259259+// std::cout << "2: \"" << path << "\"" << std::endl;
211260212212- std::cout << "requested " << path << std::endl;
261261+ // Gemini
262262+ if (request_scheme == 1) {
263263+ maple::gemini::handle_client(gemini_files, path, response);
264264+ } else { // Titan
265265+ if (!titan) {
266266+ response << "20 text/gemini\r\nThe server (Maple) does not have "
267267+ "Titan support enabled!";
268268+ } else {
269269+ maple::titan::handle_client(
270270+ response,
271271+ path,
272272+ titan_token,
273273+ titan_max_size
274274+ );
275275+ }
276276+ }
213277214214- SSL_write(ssl, response.str().c_str(), static_cast<int>(response.str().size()));
278278+ SSL_write(
279279+ ssl,
280280+ response.str().c_str(),
281281+ static_cast<int>(response.str().size())
282282+ );
283283+ } else {
284284+ std::cout << "received a request with an unsupported url scheme"
285285+ << std::endl;
286286+ }
215287 }
216288217289 SSL_shutdown(ssl);
···220292 }
221293}
222294223223-auto exit_with[[noreturn]](const char *message, bool ssl) -> void {
224224- perror(message);
225225- if (ssl) { ERR_print_errors_fp(stderr); }
226226- std::exit(EXIT_FAILURE);
295295+namespace maple {
296296+ auto exit_with[[noreturn]](const char *message, bool ssl) -> void {
297297+ perror(message);
298298+ if (ssl) { ERR_print_errors_fp(stderr); }
299299+ std::exit(EXIT_FAILURE);
300300+ }
227301}
+13
maple/maple.hh
···11+#ifndef MAPLE_HH
22+#define MAPLE_HH
33+44+#include <openssl/ssl.h>
55+66+namespace maple {
77+ static int maple_socket;
88+ static SSL_CTX *ssl_context;
99+1010+ auto exit_with[[noreturn]](const char *, bool) -> void;
1111+}
1212+1313+#endif // MAPLE_HH
+168
maple/titan.cc
···11+/*
22+ * This file is part of Maple <https://github.com/gemrest/maple>.
33+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
44+ *
55+ * This program is free software: you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
77+ * the Free Software Foundation, version 3.
88+ *
99+ * This program is distributed in the hope that it will be useful, but
1010+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1212+ * General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ *
1717+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
1818+ * SPDX-License-Identifier: GPL-3.0-only
1919+ */
2020+2121+#include <map>
2222+#include <fstream>
2323+#include <vector>
2424+2525+#include "titan.hh"
2626+2727+namespace maple::titan {
2828+ auto parameters_to_map(
2929+ const std::vector<std::string> ¶meters
3030+ ) -> std::map<std::string, std::string> {
3131+ std::map<std::string, std::string> parameters_map;
3232+3333+ for (auto parameter : parameters) {
3434+ // Find the key in `parameter`
3535+ size_t parameter_delimiter_position = parameter.find('=');
3636+ std::string key = parameter.substr(
3737+ 0,
3838+ parameter_delimiter_position
3939+ );
4040+4141+ // Remove the key in `parameter`
4242+ parameter.erase(0, parameter_delimiter_position + 1);
4343+4444+ // Add the key and value to `parameters_map`
4545+ parameters_map[key] = parameter;
4646+ }
4747+4848+ return parameters_map;
4949+ }
5050+5151+ auto handle_client(
5252+ std::stringstream &response,
5353+ std::string path,
5454+ const std::string &titan_token,
5555+ size_t titan_max_size
5656+ ) -> void {
5757+ std::vector<std::string> parameters;
5858+ // Find path in `path`
5959+ size_t delimiter_position = path.find(';');
6060+ std::string update_path = path.substr(0, delimiter_position);
6161+ std::string body = path.substr(path.find('\n') + 1, path.length() - 1);
6262+6363+ path.erase(path.find('\n') - 1, path.length() - 1);
6464+ // parameters.push_back(update_path);
6565+ path.erase(0, delimiter_position + 1); // Remove path from `path`
6666+6767+ // Find mime parameter in `path`
6868+ delimiter_position = path.find(';');
6969+7070+ parameters.push_back(path.substr(0, delimiter_position));
7171+ path.erase(0, delimiter_position + 1); // Remove mime parameter from `path`
7272+7373+ // Find size parameter in `path`
7474+ delimiter_position = path.find(';');
7575+7676+ parameters.push_back(path.substr(0, delimiter_position));
7777+7878+ // Find token parameter in `path`
7979+ delimiter_position = path.find(';');
8080+8181+ // Since the token is optional, only get and assign the token
8282+ // parameters value if it exists.
8383+ if (delimiter_position != std::string::npos) {
8484+ parameters.push_back(path.substr(
8585+ delimiter_position + 1,
8686+ path.length() - 1
8787+ ));
8888+ }
8989+9090+/// Check if a parameter exists within a `std::vector` of Titan
9191+/// parameters.
9292+ /* auto parameter_exists = [](
9393+ const std::vector<std::string> &_parameters,
9494+ const std::string ¶meter
9595+ ) -> bool {
9696+ return std::any_of(
9797+ _parameters.begin(),
9898+ _parameters.end(),
9999+ [&](const std::string &s) -> bool {
100100+ return s.find(parameter) != std::string::npos;
101101+ }
102102+ );
103103+ }; */
104104+105105+ std::map<std::string, std::string> parameters_map =
106106+ maple::titan::parameters_to_map(parameters);
107107+108108+ // Make sure all tokens have been supplied
109109+ for (;;) {
110110+ if (parameters_map.find("mime") == parameters_map.end()) {
111111+ response << "20 text/gemini\r\nThe serve (Maple) did not "
112112+ "receive a mime parameter!";
113113+ break;
114114+ }
115115+ if (parameters_map.find("size") == parameters_map.end()) {
116116+ response << "20 text/gemini\r\nThe serve (Maple) did not "
117117+ "receive a size parameter!";
118118+119119+ break;
120120+ }
121121+ if (!titan_token.empty()
122122+ && parameters_map.find("token") == parameters_map.end())
123123+ {
124124+ response << "20 text/gemini\r\nThe serve (Maple) did not "
125125+ "receive a token parameter!";
126126+127127+ break;
128128+ }
129129+130130+ try {
131131+ size_t body_size = static_cast<size_t>(
132132+ std::stoi(parameters_map["size"])
133133+ );
134134+135135+ if (body_size > titan_max_size) {
136136+ response << "20 text/gemini\r\nThe server (Maple) received a body "
137137+ << "which is larger than the maximum allowed body size ("
138138+ << titan_max_size << ").";
139139+140140+ break;
141141+ }
142142+ } catch (...) {
143143+ response << "20 text/gemini\r\nThe server (Maple) could not interpret "
144144+ "the size parameter as an integer!";
145145+146146+ break;
147147+ }
148148+149149+ if (update_path == "/") {
150150+ update_path = "/index.gmi";
151151+ }
152152+153153+ if (parameters_map["token"] == titan_token) {
154154+ std::ofstream file(".maple/gmi" + update_path);
155155+156156+ file << body;
157157+158158+ response << "20 text/gemini\r\nSuccessfully wrote "
159159+ << body.length() << " bytes to " << update_path << '!';
160160+ } else {
161161+ response << "20 text/gemini\r\nThe server (Maple) wrote to "
162162+ << update_path;
163163+ }
164164+165165+ break;
166166+ }
167167+ }
168168+}
+40
maple/titan.hh
···11+/*
22+ * This file is part of Maple <https://github.com/gemrest/maple>.
33+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
44+ *
55+ * This program is free software: you can redistribute it and/or modify
66+ * it under the terms of the GNU General Public License as published by
77+ * the Free Software Foundation, version 3.
88+ *
99+ * This program is distributed in the hope that it will be useful, but
1010+ * WITHOUT ANY WARRANTY; without even the implied warranty of
1111+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1212+ * General Public License for more details.
1313+ *
1414+ * You should have received a copy of the GNU General Public License
1515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1616+ *
1717+ * Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
1818+ * SPDX-License-Identifier: GPL-3.0-only
1919+ */
2020+2121+#ifndef TITAN_HH
2222+#define TITAN_HH
2323+2424+#include <sstream>
2525+2626+namespace maple::titan {
2727+ /// Convert a `std::vector` of Titan parameters into a key/ value `std::map`
2828+ auto parameters_to_map(
2929+ const std::vector<std::string> &
3030+ ) -> std::map<std::string, std::string>;
3131+3232+ auto handle_client(
3333+ std::stringstream &,
3434+ std::string,
3535+ const std::string &,
3636+ size_t
3737+ ) -> void;
3838+}
3939+4040+#endif // TITAN_HH