commit - 529cc6f4bf81cb3c4dc794c56969c93ee40fe333
commit + b20335632804754482ef930b97f325c08c4ac8fc
blob - 927d234350bb21ec83c22f006949aa24f2b5e23c
blob + 8931ba4f492fc02e35c0967addd746637eaa662b
--- Makefile
+++ Makefile
${MAKE} -C vostokd
run_server: server
- ./vostokd/vostokd -c cert/server.crt -k cert/server.key -d static/
+ ./vostokd/vostokd -c cert/server.crt -k cert/server.key -f static/
blob - fb1b6382ef772db709bc9de4897bc5b28d551339
blob + a99927eecbfa09bd8a7e7ed7903d4d3205a28c69
--- shared/gemini.cc
+++ shared/gemini.cc
const std::array<char, 1> SPACE{' '};
const status_t STATUS_20_SUCCESS{'2', '0'};
+const status_t STATUS_40_TEMPORARY_FAILURE{'4', '0'};
const status_t STATUS_50_PERMANENT_FAILURE{'5', '0'};
+const status_t STATUS_51_NOT_FOUND{'5', '1'};
const status_t STATUS_53_PROXY_REQUEST_REFUSED{'5', '3'};
const status_t STATUS_59_BAD_REQUEST{'5', '9'};
blob - 2230496a1499e62f60cfa32596d71516e0576f7f
blob + 3b9b02c570f7adc25dcbcbb98661afa9c95160af
--- shared/gemini.h
+++ shared/gemini.h
using status_t = std::array<char, 2>;
extern const status_t STATUS_20_SUCCESS;
+extern const status_t STATUS_40_TEMPORARY_FAILURE;
extern const status_t STATUS_50_PERMANENT_FAILURE;
+extern const status_t STATUS_51_NOT_FOUND;
extern const status_t STATUS_53_PROXY_REQUEST_REFUSED;
extern const status_t STATUS_59_BAD_REQUEST;
blob - aa8fb179940b64dd6a25670b63d036b0023b888f
blob + a07cc088e96481e3b281957882e83d37e0e73818
--- vostokd/command_line_arguments.cc
+++ vostokd/command_line_arguments.cc
error::g_log << "\t-p PORT : TCP/IP port number (1965 by default)" << std::endl;
error::g_log << "\t-c FILE : Server certificate file [REQUIRED]" << std::endl;
error::g_log << "\t-k FILE : Server key file [REQUIRED]" << std::endl;
- error::g_log << "\t-d DIR : Path to data directory [REQUIRED]" << std::endl;
+ error::g_log << "\t-f PATH : Path to file system data [REQUIRED]" << std::endl;
return false;
}
{
int ch;
char *p = nullptr;
- while ((ch = getopt(argc, argv, "a:p:c:k:d:")) != -1) {
+ while ((ch = getopt(argc, argv, "a:p:c:k:f:")) != -1) {
switch (ch) {
case 'a':
m_addr = optarg;
case 'k':
m_key_file = optarg;
break;
- case 'd':
- m_directory.reset(open(optarg, O_RDONLY | O_DIRECTORY));
+ case 'f':
+ m_directory.reset(open(optarg, O_RDONLY));
if (!m_directory)
{
error::occurred(
blob - 1feff3e2ae1dc163916656fc132100293d01c78d
blob + 72390f99d42cc57e7c7fba2e9f80a4437334c892
--- vostokd/vostokd.cc
+++ vostokd/vostokd.cc
#include "cut_null"
#include "unique_fd"
+#include <fcntl.h>
+#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread>
-
+#include <vector>
namespace vostok
const char sz_root_traverse[] = "Wrong traverse";
const auto root_traverse = cut_null(sz_root_traverse);
+
+const char sz_not_found[] = "Not found";
+const auto not_found = cut_null(sz_not_found);
+
+const char sz_temporary_failure[] = "Temporary failure";
+const auto temporary_failure = cut_null(sz_temporary_failure);
} // namespace meta
-void client_thread(transport::accepted_context::value raw_value)
+void client_thread(transport::accepted_context::value raw_value, int directory_fd)
{
const transport::accepted_context ctx{raw_value};
assert(ctx);
break;
}
- error::g_log << "Requested URL: \"" << zs_url_path.data() << "\"" << std::endl;
+ const unique_fd opened_file{openat(directory_fd, zs_url_path.data(), O_RDONLY)};
+ if (!opened_file)
+ {
+ const auto error_code = errno;
+ error::occurred(
+ [&zs_url_path]
+ {
+ error::g_log << "Open file \"" << zs_url_path.data() << "\"";
+ },
+ error::print{error_code}
+ );
+ switch (error_code)
+ {
+ case ENOTDIR:
+ case ENOENT:
+ case EACCES:
+ case ELOOP:
+ case EISDIR:
+ case ENXIO:
+ transport::send_response(ctx.get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::not_found);
+ break;
+ default:
+ transport::send_response(ctx.get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::temporary_failure);
+ break;
+ }
+ return;
+ }
+ struct stat sb{};
+ if (fstat(opened_file.get(), &sb) == -1)
+ {
+ error::occurred(
+ [&zs_url_path]
+ {
+ error::g_log << "Stat file \"" << zs_url_path.data() << "\"";
+ },
+ error::print{}
+ );
+ transport::send_response(ctx.get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::temporary_failure);
+ return;
+ }
+ if (S_ISDIR(sb.st_mode))
+ {
+ error::occurred(
+ [&zs_url_path]
+ {
+ error::g_log << "Open file \"" << zs_url_path.data() << "\"";
+ },
+ error::print{EISDIR}
+ );
+ transport::send_response(ctx.get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::not_found);
+ return;
+ }
// > If <META> is an empty string, the MIME type MUST default to "text/gemini; charset=utf-8".
transport::send_response(ctx.get_ctx(), gemini::STATUS_20_SUCCESS, {});
- static const char content[] = "# Vostok server\r\n\r\n...work-in-progress...";
- transport::send(ctx.get_ctx(), cut_null(content));
+ for (; ; )
+ {
+ const auto ret = read(opened_file.get(), buffer.data(), buffer.size());
+ if (ret == -1)
+ {
+ error::occurred(
+ [&zs_url_path]
+ {
+ error::g_log << "Read file \"" << zs_url_path.data() << "\"";
+ },
+ error::print{}
+ );
+ return;
+ }
+ const auto readed = static_cast<size_t>(ret);
+ if (readed)
+ {
+ if (!transport::send(ctx.get_ctx(), span<const char>{buffer.data(), readed}))
+ return;
+ }
+ if (readed < buffer.size())
+ break;
+ }
+ error::g_log << "20 " << "\"" << zs_url_path.data() << "\"" << std::endl;
}
-bool server_loop(int server_socket, not_null<struct tls *>ctx)
+bool server_loop(int server_socket, not_null<struct tls *>ctx, int directory_fd)
{
error::g_log << "🚀 Vostok server listening..." << std::endl;
for (; ; )
try
{
- std::thread{client_thread, client_ctx.get()}.detach();
+ std::thread{client_thread, client_ctx.get(), directory_fd}.detach();
client_ctx.release(); // move ownership to thread
}
catch (const std::system_error &e)
return false;
}
- return server_loop(server_socket.get(), ctx.get());
+ return server_loop(server_socket.get(), ctx.get(), args.m_directory.get());
}