MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
fileserve.cpp
Go to the documentation of this file.
1// generated example for library MXNetwork
3#include <algorithm>
4#include <atomic>
5#include <charconv>
6#include <cstddef>
7#include <cstdlib>
8#include <filesystem>
9#include <fstream>
10#include <iostream>
11#include <limits>
12#include <optional>
13#include <string>
14#include <string_view>
15#include <thread>
16#include <vector>
17
18#ifdef _WIN32
19#include <windows.h>
20#else
21#include <signal.h>
22#include <sys/socket.h>
23#endif
24
25namespace {
26
27 constexpr unsigned int port_min = 1024;
28 constexpr unsigned int port_max = 4951;
29 constexpr size_t buffer_size = 4096;
30 constexpr int progress_width = 48;
31
32 std::atomic<bool> active_loop{false};
33 std::atomic<mxnetwork::Socket *> listen_socket{nullptr};
34
35 void request_stop() {
36 active_loop.store(false);
37 mxnetwork::Socket *listener = listen_socket.load();
38 if (listener == nullptr) {
39 return;
40 }
41
42 const mx_socket_fd fd = listener->sockfd();
43 if (fd != NULL_SOCKET) {
44#ifdef _WIN32
45 shutdown(fd, SD_BOTH);
46#else
47 shutdown(fd, SHUT_RDWR);
48#endif
49 }
50 listener->close();
51 }
52
53 bool send_text(mxnetwork::Socket &sock, std::string_view text) {
54 return sock.write_all(text.data(), text.size()) == static_cast<ssize_t>(text.size());
55 }
56
57 void send_error(mxnetwork::Socket &sock, std::string_view message) {
58 if (!send_text(sock, message)) {
59 std::cerr << "fileserve: Error could not send data.\n";
60 }
61 }
62
63 bool valid_filename(std::string_view filename) {
64 if (filename.empty() || filename == "." || filename == "..") {
65 return false;
66 }
67 if (filename.find('/') != std::string_view::npos || filename.find('\\') != std::string_view::npos) {
68 return false;
69 }
70
71 const std::filesystem::path path{std::string(filename)};
72 return !path.is_absolute() && !path.has_parent_path() && path.filename() == path;
73 }
74
76 std::error_code error;
77 for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(".", error)) {
78 if (error) {
79 std::cerr << "fileserve: Error reading directory: " << error.message() << "\n";
80 send_error(sock, "error: 100\r\n");
81 return;
82 }
83 if (!entry.is_regular_file(error) || error) {
84 error.clear();
85 continue;
86 }
87
88 const std::string filename = entry.path().filename().string() + "\n";
89 if (!send_text(sock, filename)) {
90 std::cerr << "fileserve: could not write data.\n";
91 return;
92 }
93 }
94
95 if (!send_text(sock, "\r\n")) {
96 std::cerr << "fileserve: could not write data.\n";
97 }
98 }
99
100 void send_file(mxnetwork::Socket &sock, std::string_view filename) {
101 if (!valid_filename(filename)) {
102 std::cerr << "fileserve: Error invalid path.\n";
103 send_error(sock, "error: 101\r\n");
104 return;
105 }
106
107 std::filesystem::path path{std::string(filename)};
108 std::error_code error;
109 const uintmax_t file_size = std::filesystem::file_size(path, error);
110 if (error) {
111 std::cerr << "fileserve: Could not stat file: " << error.message() << "\n";
112 send_error(sock, "error: 103\r\n");
113 return;
114 }
115 if (file_size > static_cast<uintmax_t>(std::numeric_limits<size_t>::max())) {
116 std::cerr << "fileserve: File too large.\n";
117 send_error(sock, "error: 104\r\n");
118 return;
119 }
120
121 std::ifstream file(path, std::ios::binary);
122 if (!file) {
123 std::cerr << "fileserve: Could not open file: " << filename << "\n";
124 send_error(sock, "error: 103\r\n");
125 return;
126 }
127
128 if (!send_text(sock, "Content-Length: " + std::to_string(file_size) + "\n")) {
129 std::cerr << "fileserve: Error could not write header.\n";
130 return;
131 }
132
133 std::vector<char> buffer(buffer_size);
134 while (file) {
135 file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
136 const std::streamsize bytes = file.gcount();
137 if (bytes <= 0) {
138 break;
139 }
140 if (sock.write_all(buffer.data(), static_cast<size_t>(bytes)) != bytes) {
141 std::cerr << "fileserve: Error could not write file data.\n";
142 return;
143 }
144 }
145 }
146
147 std::optional<std::string> read_command(mxnetwork::Socket &sock) {
148 std::string command;
149 std::vector<char> buffer(buffer_size);
150
151 while (active_loop.load()) {
152 const ssize_t bytes = sock.read(buffer.data(), buffer.size(), 0);
153 if (bytes <= 0) {
154 return std::nullopt;
155 }
156
157 command.append(buffer.data(), static_cast<size_t>(bytes));
158 const size_t end = command.find("\r\n");
159 if (end != std::string::npos) {
160 command.resize(end);
161 return command;
162 }
163 if (command.size() > buffer_size) {
164 send_error(sock, "error: 102\r\n");
165 return std::nullopt;
166 }
167 }
168
169 return std::nullopt;
170 }
171
173 while (active_loop.load()) {
174 std::optional<std::string> command = read_command(sock);
175 if (!command) {
176 break;
177 }
178
179 std::cout << "fileserve: command: " << *command << "\n";
180 if (*command == "ls") {
181 list_directory(sock);
182 } else if (command->starts_with("get: ")) {
183 const std::string_view filename{command->data() + 5, command->size() - 5};
184 std::cout << "fileserve: sending file: " << filename << "\n";
185 send_file(sock, filename);
186 } else if (*command == "exit:") {
187 request_stop();
188 break;
189 } else {
190 std::cout << "fileserve: invalid command.\n";
191 send_error(sock, "error: 102\r\n");
192 }
193 }
194
195 std::cout << "Exiting thread socket[" << sock.sockfd() << "]\n";
196 }
197
198#ifdef _WIN32
199 BOOL WINAPI console_handler(DWORD signal) {
200 if (signal == CTRL_C_EVENT || signal == CTRL_CLOSE_EVENT) {
201 request_stop();
202 return TRUE;
203 }
204 return FALSE;
205 }
206#else
207 void listen_signal(int) {
208 active_loop.store(false);
209 }
210#endif
211
212 void listen_server(std::string_view port) {
214 if (!sock.listen(port, 5)) {
215 std::cerr << "file_serve: Error on listen..\n";
216 return;
217 }
218 if (!sock.setblocking(true)) {
219 std::cerr << "file_serve: Error setting listener blocking mode.\n";
220 return;
221 }
222
223 std::cout << "file_serve: Listening on port " << port << "\n";
224 active_loop.store(true);
225 listen_socket.store(&sock);
226 while (active_loop.load()) {
227 std::optional<mxnetwork::Socket> new_socket = sock.accept();
228 if (!new_socket) {
229 if (active_loop.load()) {
230 std::cerr << "fileserve: accept failed.\n";
231 }
232 continue;
233 }
234
235 std::thread thread(process_input, std::move(*new_socket));
236 thread.detach();
237 }
238
239 listen_socket.store(nullptr);
240 std::cout << "fileserve: Exiting...\n";
241 }
242
243 void print_progress(int &prev, size_t length, size_t progress) {
244 if (length == 0) {
245 std::cout << "\r[ Downloading ] - 0%" << std::flush;
246 return;
247 }
248 if (progress > length) {
249 progress = length;
250 }
251
252 const double ratio = static_cast<double>(progress) / static_cast<double>(length);
253 const int percent = static_cast<int>(ratio * 100.0);
254 if (prev == percent) {
255 return;
256 }
257 prev = percent;
258
259 const int filled = static_cast<int>(ratio * progress_width);
260 std::cout << "\r[";
261 for (int i = 0; i < progress_width; ++i) {
262 std::cout << (i < filled ? '=' : ' ');
263 }
264 std::cout << "] - " << percent << "%" << std::flush;
265 }
266
267 std::optional<size_t> parse_content_length(std::string_view data) {
268 constexpr std::string_view prefix = "Content-Length: ";
269 if (!data.starts_with(prefix)) {
270 return std::nullopt;
271 }
272
273 const size_t line_end = data.find('\n');
274 if (line_end == std::string_view::npos) {
275 return std::nullopt;
276 }
277
278 size_t length = 0;
279 const std::string_view value = data.substr(prefix.size(), line_end - prefix.size());
280 const auto result = std::from_chars(value.data(), value.data() + value.size(), length);
281 if (result.ec != std::errc() || result.ptr != value.data() + value.size()) {
282 return std::nullopt;
283 }
284 return length;
285 }
286
287 std::optional<std::string> parse_get_filename(std::string_view command) {
288 constexpr std::string_view prefix = "get: ";
289 if (!command.starts_with(prefix)) {
290 return std::nullopt;
291 }
292
293 std::string filename{command.substr(prefix.size())};
294 if (!valid_filename(filename)) {
295 return std::nullopt;
296 }
297 return filename;
298 }
299
301 std::string output;
302 std::vector<char> buffer(buffer_size);
303 while (true) {
304 const ssize_t bytes = sock.read(buffer.data(), buffer.size(), 0);
305 if (bytes <= 0) {
306 break;
307 }
308
309 output.append(buffer.data(), static_cast<size_t>(bytes));
310 const size_t end = output.find("\r\n");
311 if (end != std::string::npos) {
312 std::cout << output.substr(0, end);
313 break;
314 }
315 }
316 }
317
318 void receive_file(mxnetwork::Socket &sock, std::string_view command) {
319 const std::optional<std::string> filename = parse_get_filename(command);
320 if (!filename) {
321 std::cerr << "fileserve: Error on interpreting get command.\n";
322 return;
323 }
324
325 std::vector<char> buffer(buffer_size);
326 const ssize_t bytes = sock.read(buffer.data(), buffer.size(), 0);
327 if (bytes <= 0) {
328 return;
329 }
330
331 std::string header_and_data(buffer.data(), static_cast<size_t>(bytes));
332 if (header_and_data.starts_with("error:")) {
333 std::cerr << "fileserve: Server returned " << header_and_data << "\n";
334 return;
335 }
336
337 const std::optional<size_t> file_size = parse_content_length(header_and_data);
338 const size_t data_start = header_and_data.find('\n');
339 if (!file_size || data_start == std::string::npos) {
340 std::cerr << "fileserve: Invalid server response.\n";
341 return;
342 }
343
344 std::ofstream file(*filename, std::ios::binary | std::ios::trunc);
345 if (!file) {
346 std::cerr << "fileserve: Could not open output file: " << *filename << "\n";
347 return;
348 }
349
350 size_t bytes_written = 0;
351 const size_t initial_offset = data_start + 1;
352 if (initial_offset < header_and_data.size()) {
353 const size_t initial_bytes = std::min(*file_size, header_and_data.size() - initial_offset);
354 file.write(header_and_data.data() + initial_offset, static_cast<std::streamsize>(initial_bytes));
355 bytes_written += initial_bytes;
356 }
357
358 int prev = -1;
359 print_progress(prev, *file_size, bytes_written);
360 while (bytes_written < *file_size) {
361 const size_t to_read = std::min(buffer.size(), *file_size - bytes_written);
362 const ssize_t read_bytes = sock.read(buffer.data(), to_read, 0);
363 if (read_bytes <= 0) {
364 std::cerr << "\nfileserve: Error reading file data.\n";
365 break;
366 }
367
368 file.write(buffer.data(), read_bytes);
369 if (!file) {
370 std::cerr << "\nfileserve: Error writing to file.\n";
371 break;
372 }
373 bytes_written += static_cast<size_t>(read_bytes);
374 print_progress(prev, *file_size, bytes_written);
375 }
376
377 print_progress(prev, *file_size, bytes_written);
378 std::cout << "\nfileserve: Saved " << bytes_written << " bytes to " << *filename << "\n";
379 }
380
381 void connect_client(std::string_view host, std::string_view port) {
382 active_loop.store(true);
384 if (!sock.connect(host, port)) {
385 std::cerr << "fileserve: Error could not connect\n";
386 return;
387 }
388
389 while (active_loop.load()) {
390 std::string input;
391 std::cout << "fileserve> ";
392 if (!std::getline(std::cin, input)) {
393 break;
394 }
395
396 const std::string command = input + "\r\n";
397 if (!send_text(sock, command)) {
398 std::cerr << "fserve: Error could not write data.\n";
399 break;
400 }
401
402 if (input == "exit:") {
403 break;
404 }
405 if (input == "help") {
406 std::cout << "program commands:\nhelp\t[this message]\nls\t[list files]\nget: <filename>\t[get file]\nexit:\t[exit program.]\n";
407 } else if (input == "ls") {
408 receive_listing(sock);
409 } else if (input.starts_with("get: ")) {
410 receive_file(sock, input);
411 } else {
412 std::cout << "fileserve: invalid command.\n";
413 }
414 }
415 }
416
417 bool parse_port(const char *value, unsigned int &port) {
418 const std::string_view text{value};
419 const auto result = std::from_chars(text.data(), text.data() + text.size(), port);
420 return result.ec == std::errc() && result.ptr == text.data() + text.size();
421 }
422
423 bool validate_port(const char *program, const char *value, unsigned int &port) {
424 if (!parse_port(value, port)) {
425 std::cerr << "fileserve: Error use:\n"
426 << program << " <port>\n";
427 return false;
428 }
429 if (port < port_min || port > port_max) {
430 std::cerr << "fileserve: Error port out of range.\nUse: " << port_min << "-" << port_max << " (suggested: 3000)\n";
431 return false;
432 }
433 return true;
434 }
435
436} // namespace
437
438int main(int argc, char **argv) {
439 mxnetwork::MXNetworkInit network_init;
441
442#ifdef _WIN32
443 if (!SetConsoleCtrlHandler(console_handler, TRUE)) {
444 std::cerr << "fileserve: Could not install console handler.\n";
445 return EXIT_FAILURE;
446 }
447#else
448 struct sigaction sa = {};
449 sa.sa_handler = listen_signal;
450 sa.sa_flags = 0;
451 if (sigaction(SIGINT, &sa, nullptr) == -1) {
452 std::cerr << "fileserve: Could not install signal handler.\n";
453 return EXIT_FAILURE;
454 }
455#endif
456
457 unsigned int port = 0;
458 try {
459 if (argc == 2) {
460 if (!validate_port(argv[0], argv[1], port)) {
461 return EXIT_FAILURE;
462 }
463 listen_server(argv[1]);
464 } else if (argc == 3) {
465 if (!validate_port(argv[0], argv[2], port)) {
466 return EXIT_FAILURE;
467 }
468 std::cout << "Connecting...\n";
469 connect_client(argv[1], argv[2]);
470 } else {
471 std::cerr << "Error use:\n"
472 << argv[0] << " <port>\t\tfor listen (server)\n"
473 << argv[0] << " <host> <port>\tfor connect (client)\n";
474 return EXIT_SUCCESS;
475 }
476 } catch (const mxnetwork::Exception &error) {
477 std::cerr << "fileserve: " << error.text() << "\n";
478 return EXIT_FAILURE;
479 }
480
481 std::cout << "fileserve: Exiting.\n";
482 return EXIT_SUCCESS;
483}
Lightweight exception wrapper for MXNetwork failures.
Definition exception.hpp:11
std::string text() const
Return the stored error text.
Definition exception.cpp:5
C++ wrapper around the MXNetwork socket API.
Definition socket.hpp:57
ssize_t write_all(const void *buf, size_t bytes)
Write exactly the requested number of bytes.
Definition socket.cpp:203
void close()
Close the socket if it is open.
Definition socket.cpp:171
bool listen(std::string_view port, int backlog)
Start listening on an Internet port.
Definition socket.cpp:100
std::optional< Socket > accept()
Accept an incoming connection.
Definition socket.cpp:120
bool setblocking(bool block)
Toggle blocking mode.
Definition socket.cpp:159
ssize_t read(void *buf, size_t bytes, int flags)
Read bytes from the socket.
Definition socket.cpp:187
mx_socket_fd sockfd() const
Return the underlying socket handle.
Definition socket.cpp:183
bool connect(const std::string_view host, const std::string_view port)
Connect to a remote Internet endpoint.
Definition socket.cpp:80
int main(void)
Definition main.cpp:7
int mx_socket_fd
Definition mxsocket.h:48
void mx_socket_ignore_pipe_signal()
Ignore SIGPIPE on platforms that require it.
Definition mxsocket.c:577
#define NULL_SOCKET
Definition mxsocket.h:62
std::optional< std::string > parse_get_filename(std::string_view command)
bool send_text(mxnetwork::Socket &sock, std::string_view text)
Definition fileserve.cpp:53
void process_input(mxnetwork::Socket sock)
void send_error(mxnetwork::Socket &sock, std::string_view message)
Definition fileserve.cpp:57
void connect_client(std::string_view host, std::string_view port)
void listen_server(std::string_view port)
bool valid_filename(std::string_view filename)
Definition fileserve.cpp:63
std::atomic< mxnetwork::Socket * > listen_socket
Definition fileserve.cpp:33
void receive_listing(mxnetwork::Socket &sock)
std::optional< std::string > read_command(mxnetwork::Socket &sock)
constexpr unsigned int port_min
Definition fileserve.cpp:27
bool parse_port(const char *value, unsigned int &port)
bool validate_port(const char *program, const char *value, unsigned int &port)
std::optional< size_t > parse_content_length(std::string_view data)
void list_directory(mxnetwork::Socket &sock)
Definition fileserve.cpp:75
std::atomic< bool > active_loop
Definition fileserve.cpp:32
constexpr unsigned int port_max
Definition fileserve.cpp:28
void send_file(mxnetwork::Socket &sock, std::string_view filename)
void print_progress(int &prev, size_t length, size_t progress)
void receive_file(mxnetwork::Socket &sock, std::string_view command)
@ TYPE_INET
IPv4 stream socket.
Definition socket.hpp:41
RAII helper that initializes and shuts down the platform socket subsystem.
Definition socket.hpp:21