22#include <sys/socket.h>
38 if (listener ==
nullptr) {
45 shutdown(fd, SD_BOTH);
47 shutdown(fd, SHUT_RDWR);
54 return sock.
write_all(text.data(), text.size()) ==
static_cast<ssize_t
>(text.size());
59 std::cerr <<
"fileserve: Error could not send data.\n";
64 if (filename.empty() || filename ==
"." || filename ==
"..") {
67 if (filename.find(
'/') != std::string_view::npos || filename.find(
'\\') != std::string_view::npos) {
71 const std::filesystem::path path{std::string(filename)};
72 return !path.is_absolute() && !path.has_parent_path() && path.filename() == path;
76 std::error_code error;
77 for (
const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(
".", error)) {
79 std::cerr <<
"fileserve: Error reading directory: " << error.message() <<
"\n";
83 if (!entry.is_regular_file(error) || error) {
88 const std::string filename = entry.path().filename().string() +
"\n";
90 std::cerr <<
"fileserve: could not write data.\n";
96 std::cerr <<
"fileserve: could not write data.\n";
102 std::cerr <<
"fileserve: Error invalid path.\n";
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);
111 std::cerr <<
"fileserve: Could not stat file: " << error.message() <<
"\n";
115 if (file_size >
static_cast<uintmax_t
>(std::numeric_limits<size_t>::max())) {
116 std::cerr <<
"fileserve: File too large.\n";
121 std::ifstream file(path, std::ios::binary);
123 std::cerr <<
"fileserve: Could not open file: " << filename <<
"\n";
128 if (!
send_text(sock,
"Content-Length: " + std::to_string(file_size) +
"\n")) {
129 std::cerr <<
"fileserve: Error could not write header.\n";
135 file.read(buffer.data(),
static_cast<std::streamsize
>(buffer.size()));
136 const std::streamsize bytes = file.gcount();
140 if (sock.
write_all(buffer.data(),
static_cast<size_t>(bytes)) != bytes) {
141 std::cerr <<
"fileserve: Error could not write file data.\n";
152 const ssize_t bytes = sock.
read(buffer.data(), buffer.size(), 0);
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) {
174 std::optional<std::string> command =
read_command(sock);
179 std::cout <<
"fileserve: command: " << *command <<
"\n";
180 if (*command ==
"ls") {
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";
186 }
else if (*command ==
"exit:") {
190 std::cout <<
"fileserve: invalid command.\n";
195 std::cout <<
"Exiting thread socket[" << sock.
sockfd() <<
"]\n";
199 BOOL WINAPI console_handler(DWORD signal) {
200 if (signal == CTRL_C_EVENT || signal == CTRL_CLOSE_EVENT) {
214 if (!sock.
listen(port, 5)) {
215 std::cerr <<
"file_serve: Error on listen..\n";
219 std::cerr <<
"file_serve: Error setting listener blocking mode.\n";
223 std::cout <<
"file_serve: Listening on port " << port <<
"\n";
227 std::optional<mxnetwork::Socket> new_socket = sock.
accept();
230 std::cerr <<
"fileserve: accept failed.\n";
240 std::cout <<
"fileserve: Exiting...\n";
245 std::cout <<
"\r[ Downloading ] - 0%" << std::flush;
248 if (progress > length) {
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) {
262 std::cout << (i < filled ?
'=' :
' ');
264 std::cout <<
"] - " << percent <<
"%" << std::flush;
268 constexpr std::string_view prefix =
"Content-Length: ";
269 if (!data.starts_with(prefix)) {
273 const size_t line_end = data.find(
'\n');
274 if (line_end == std::string_view::npos) {
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()) {
288 constexpr std::string_view prefix =
"get: ";
289 if (!command.starts_with(prefix)) {
293 std::string filename{command.substr(prefix.size())};
304 const ssize_t bytes = sock.
read(buffer.data(), buffer.size(), 0);
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);
321 std::cerr <<
"fileserve: Error on interpreting get command.\n";
326 const ssize_t bytes = sock.
read(buffer.data(), buffer.size(), 0);
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";
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";
344 std::ofstream file(*filename, std::ios::binary | std::ios::trunc);
346 std::cerr <<
"fileserve: Could not open output file: " << *filename <<
"\n";
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;
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";
368 file.write(buffer.data(), read_bytes);
370 std::cerr <<
"\nfileserve: Error writing to file.\n";
373 bytes_written +=
static_cast<size_t>(read_bytes);
378 std::cout <<
"\nfileserve: Saved " << bytes_written <<
" bytes to " << *filename <<
"\n";
384 if (!sock.
connect(host, port)) {
385 std::cerr <<
"fileserve: Error could not connect\n";
391 std::cout <<
"fileserve> ";
392 if (!std::getline(std::cin, input)) {
396 const std::string command = input +
"\r\n";
398 std::cerr <<
"fserve: Error could not write data.\n";
402 if (input ==
"exit:") {
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") {
409 }
else if (input.starts_with(
"get: ")) {
412 std::cout <<
"fileserve: invalid command.\n";
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();
423 bool validate_port(
const char *program,
const char *value,
unsigned int &port) {
425 std::cerr <<
"fileserve: Error use:\n"
426 << program <<
" <port>\n";
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";
438int main(
int argc,
char **argv) {
443 if (!SetConsoleCtrlHandler(console_handler, TRUE)) {
444 std::cerr <<
"fileserve: Could not install console handler.\n";
448 struct sigaction sa = {};
451 if (sigaction(SIGINT, &sa,
nullptr) == -1) {
452 std::cerr <<
"fileserve: Could not install signal handler.\n";
457 unsigned int port = 0;
464 }
else if (argc == 3) {
468 std::cout <<
"Connecting...\n";
471 std::cerr <<
"Error use:\n"
472 << argv[0] <<
" <port>\t\tfor listen (server)\n"
473 << argv[0] <<
" <host> <port>\tfor connect (client)\n";
477 std::cerr <<
"fileserve: " << error.
text() <<
"\n";
481 std::cout <<
"fileserve: Exiting.\n";
Lightweight exception wrapper for MXNetwork failures.
std::string text() const
Return the stored error text.
C++ wrapper around the MXNetwork socket API.
ssize_t write_all(const void *buf, size_t bytes)
Write exactly the requested number of bytes.
void close()
Close the socket if it is open.
bool listen(std::string_view port, int backlog)
Start listening on an Internet port.
std::optional< Socket > accept()
Accept an incoming connection.
bool setblocking(bool block)
Toggle blocking mode.
ssize_t read(void *buf, size_t bytes, int flags)
Read bytes from the socket.
mx_socket_fd sockfd() const
Return the underlying socket handle.
bool connect(const std::string_view host, const std::string_view port)
Connect to a remote Internet endpoint.
void mx_socket_ignore_pipe_signal()
Ignore SIGPIPE on platforms that require it.
std::optional< std::string > parse_get_filename(std::string_view command)
bool send_text(mxnetwork::Socket &sock, std::string_view text)
constexpr size_t buffer_size
void process_input(mxnetwork::Socket sock)
void send_error(mxnetwork::Socket &sock, std::string_view message)
void connect_client(std::string_view host, std::string_view port)
void listen_server(std::string_view port)
constexpr int progress_width
bool valid_filename(std::string_view filename)
std::atomic< mxnetwork::Socket * > listen_socket
void receive_listing(mxnetwork::Socket &sock)
std::optional< std::string > read_command(mxnetwork::Socket &sock)
constexpr unsigned int port_min
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)
std::atomic< bool > active_loop
constexpr unsigned int port_max
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.
RAII helper that initializes and shuts down the platform socket subsystem.