Commit Diff


commit - 529cc6f4bf81cb3c4dc794c56969c93ee40fe333
commit + b20335632804754482ef930b97f325c08c4ac8fc
blob - 927d234350bb21ec83c22f006949aa24f2b5e23c
blob + 8931ba4f492fc02e35c0967addd746637eaa662b
--- Makefile
+++ Makefile
@@ -9,4 +9,4 @@ server:
 	${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
@@ -13,7 +13,9 @@ const std::array<char, 2> CRLF{'\r', '\n'};
 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
@@ -19,7 +19,9 @@ extern const std::array<char, 1> SPACE;
 
 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
@@ -27,7 +27,7 @@ bool usage(const char *program)
     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;
 }
@@ -39,7 +39,7 @@ bool command_line_arguments::parse_command_line(int ar
 {
     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;
@@ -59,8 +59,8 @@ bool command_line_arguments::parse_command_line(int ar
         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
@@ -7,12 +7,14 @@
 #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
@@ -43,10 +45,16 @@ const auto non_gemini = cut_null(sz_non_gemini);
 
 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);
@@ -80,17 +88,90 @@ void client_thread(transport::accepted_context::value 
         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 (; ; )
@@ -117,7 +198,7 @@ bool server_loop(int server_socket, not_null<struct tl
 
         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)
@@ -171,7 +252,7 @@ bool main(const command_line_arguments &args)
         return false;
     }
 
-    return server_loop(server_socket.get(), ctx.get());
+    return server_loop(server_socket.get(), ctx.get(), args.m_directory.get());
 }