Commit Diff


commit - aedb452477c60551dc13c223da74f0878488caeb
commit + 744cd5d55114a8cc73324a86d0e654c8115b049b
blob - 49612ff96d3235a80905edc5971fcfb2421069b8
blob + 353cef1cf625b5329877f12bc183fb59a1ccf013
--- tests/test_open_file.cc
+++ tests/test_open_file.cc
@@ -28,106 +28,158 @@ TEST_START(test_open_file)
     struct stat sb1{};
     IS_TRUE_ERRNO(fstat(dir.get(), &sb1) != -1, "Stat file " << ss.str().c_str())
 
+    {
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "", opened_fd) == open_file::SUCCESS_DIRECTORY);
 
-    std::string file_name;
+        UniqueFd not_opened_fd;
+        IS_TRUE(open_file::open(opened_fd.get(), "index.gmi", not_opened_fd) == open_file::ERROR_NOT_EXIST);
 
+        struct stat sb2{};
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
+        IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
+    }
     {
         UniqueFd opened_fd;
-        file_name = "";
-        IS_TRUE(open_file::open(dir.get(), file_name, opened_fd) == open_file::OPENED_DIRECTORY);
+        IS_TRUE(open_file::open(dir.get(), "/", opened_fd) == open_file::SUCCESS_DIRECTORY);
 
+        UniqueFd not_opened_fd;
+        IS_TRUE(open_file::open(opened_fd.get(), "index.gmi", not_opened_fd) == open_file::ERROR_NOT_EXIST);
+
         struct stat sb2{};
         IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
     {
         UniqueFd opened_fd;
-        file_name = "/";
-        IS_TRUE(open_file::open(dir.get(), file_name, opened_fd) == open_file::OPENED_DIRECTORY);
+        IS_TRUE(open_file::open(dir.get(), ".", opened_fd) == open_file::SUCCESS_DIRECTORY);
 
+        UniqueFd not_opened_fd;
+        IS_TRUE(open_file::open(opened_fd.get(), "index.gmi", not_opened_fd) == open_file::ERROR_NOT_EXIST);
+
         struct stat sb2{};
         IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
+
     {
         UniqueFd opened_fd;
-        file_name = "non-existent-file";
-        IS_TRUE(open_file::open(dir.get(), file_name, opened_fd) == open_file::NOT_FOUND);
+        IS_TRUE(open_file::open(dir.get(), "non-existent-file", opened_fd) == open_file::ERROR_NOT_EXIST);
     }
     {
         UniqueFd opened_fd;
-        file_name = "non-existent-dir/file";
-        IS_TRUE(open_file::open(dir.get(), file_name, opened_fd) == open_file::NOT_FOUND);
+        IS_TRUE(open_file::open(dir.get(), "non-existent-dir/file", opened_fd) == open_file::ERROR_NOT_EXIST);
     }
 
     const UniqueFd index_gmi{openat(dir.get(), "index.gmi", O_RDONLY | O_CREAT | O_EXCL, S_IRWXU)};
     IS_TRUE_ERRNO(index_gmi, "Create file " << ss.str().c_str() << "/index.gmi");
     IS_TRUE_ERRNO(fstat(index_gmi.get(), &sb1) != -1, "Stat file " << ss.str().c_str() << "/index.gmi");
-/*
+
+
     {
-        OpenedFile opened_file;
-        file_name = "";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENED);
-        IS_TRUE(opened_file.get_path() == "index.gmi");
+        UniqueFd opened_dir;
+        IS_TRUE(open_file::open(dir.get(), "", opened_dir) == open_file::SUCCESS_DIRECTORY);
 
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(opened_dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
         struct stat sb2{};
-        IS_TRUE_ERRNO(fstat(opened_file.get_fd(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/index.gmi");
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
+    {
+        UniqueFd opened_dir;
+        IS_TRUE(open_file::open(dir.get(), "/", opened_dir) == open_file::SUCCESS_DIRECTORY);
 
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(opened_dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
+        struct stat sb2{};
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
+        IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
+    }
     {
-        OpenedFile opened_file;
-        file_name = "index.gmi";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENED);
-        IS_TRUE(opened_file.get_path() == "index.gmi");
+        UniqueFd opened_dir;
+        IS_TRUE(open_file::open(dir.get(), ".", opened_dir) == open_file::SUCCESS_DIRECTORY);
+
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(opened_dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
         struct stat sb2{};
-        IS_TRUE_ERRNO(fstat(opened_file.get_fd(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/index.gmi");
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
 
+    {
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
+        struct stat sb2{};
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
+        IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
+    }
+
     IS_TRUE_ERRNO(mkdirat(dir.get(), "subdir", S_IRWXU) != -1,  "Create directory " << ss.str().c_str() << "/subdir");
+    const UniqueFd subdir{openat(dir.get(), "subdir", O_RDONLY | O_DIRECTORY)};
+    IS_TRUE_ERRNO(subdir, "Open directory " << ss.str().c_str() << "/subdir");
+    IS_TRUE_ERRNO(fstat(subdir.get(), &sb1) != -1, "Stat file " << ss.str().c_str())
+
     {
-        OpenedFile opened_file;
-        file_name = "subdir";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::NOT_FOUND);
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "subdir", opened_fd) == open_file::SUCCESS_DIRECTORY);
+
+        UniqueFd not_opened_fd;
+        IS_TRUE(open_file::open(opened_fd.get(), "index.gmi", not_opened_fd) == open_file::ERROR_NOT_EXIST);
+
+        struct stat sb2{};
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
+        IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
     {
-        OpenedFile opened_file;
-        file_name = "subdir/";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::NOT_FOUND);
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "subdir/", opened_fd) == open_file::SUCCESS_DIRECTORY);
+
+        UniqueFd not_opened_fd;
+        IS_TRUE(open_file::open(opened_fd.get(), "index.gmi", not_opened_fd) == open_file::ERROR_NOT_EXIST);
+
+        struct stat sb2{};
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
+        IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
-    const UniqueFd subdir{openat(dir.get(), "subdir", O_RDONLY | O_DIRECTORY)};
-    IS_TRUE_ERRNO(subdir, "Open directory " << ss.str().c_str() << "/subdir");
 
     const UniqueFd sub_index_gmi{openat(subdir.get(), "index.gmi", O_RDONLY | O_CREAT | O_EXCL, S_IRWXU)};
     IS_TRUE_ERRNO(sub_index_gmi, "Create file " << ss.str().c_str() << "/subdir/index.gmi");
     IS_TRUE_ERRNO(fstat(sub_index_gmi.get(), &sb1) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
 
     {
-        OpenedFile opened_file;
-        file_name = "subdir";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENED);
-        IS_TRUE(opened_file.get_path() == "index.gmi");
+        UniqueFd opened_dir;
+        IS_TRUE(open_file::open(dir.get(), "subdir", opened_dir) == open_file::SUCCESS_DIRECTORY);
+
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(opened_dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
         struct stat sb2{};
-        IS_TRUE_ERRNO(fstat(opened_file.get_fd(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
     {
-        OpenedFile opened_file;
-        file_name = "subdir/";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENED);
-        IS_TRUE(opened_file.get_path() == "index.gmi");
+        UniqueFd opened_dir;
+        IS_TRUE(open_file::open(dir.get(), "subdir/", opened_dir) == open_file::SUCCESS_DIRECTORY);
+
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(opened_dir.get(), "index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
         struct stat sb2{};
-        IS_TRUE_ERRNO(fstat(opened_file.get_fd(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
+
     {
-        OpenedFile opened_file;
-        file_name = "subdir/index.gmi";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENED);
-        IS_TRUE(opened_file.get_path() ==  "subdir/index.gmi");
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "subdir/index.gmi", opened_fd) == open_file::SUCCESS_FILE);
+
         struct stat sb2{};
-        IS_TRUE_ERRNO(fstat(opened_file.get_fd(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
+        IS_TRUE_ERRNO(fstat(opened_fd.get(), &sb2) != -1, "Stat file " << ss.str().c_str());
         IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
     }
 
@@ -135,15 +187,13 @@ TEST_START(test_open_file)
     IS_TRUE_ERRNO(eperm, "Create file " << ss.str().c_str() << "/subdir/index.gmi");
 
     {
-        OpenedFile opened_file;
-        file_name = "subdir/EPERM.gmi";
-        IS_TRUE(opened_file.open(dir.get(), file_name) == OpenedFile::OPENING_ERROR);
+        UniqueFd opened_fd;
+        IS_TRUE(open_file::open(dir.get(), "subdir/EPERM.gmi", opened_fd) == open_file::ERROR);
     }
 
     IS_TRUE_ERRNO(unlinkat(subdir.get(), "EPERM.gmi", 0) != -1, "Remove file " << ss.str().c_str() << "/subdir/EPERM.gmi");
-    IS_TRUE_ERRNO(unlinkat(subdir.get(), "index.gmi", 0) != -1, "Remove file " << ss.str().c_str() << "/subdir/index.gmi");
+    IS_TRUE_ERRNO(unlinkat(subdir.get(), "index.gmi", 0) != -1, "Remove file " << ss.str().c_str() << "/subdir/index.gmi");    
     IS_TRUE_ERRNO(unlinkat(dir.get(), "subdir", AT_REMOVEDIR) != -1, "Remove directory " << ss.str().c_str() << "/subdir");
-*/    
     IS_TRUE_ERRNO(unlinkat(dir.get(), "index.gmi", 0) != -1, "Remove file " << ss.str().c_str() << "/index.gmi");    
     IS_TRUE_ERRNO(rmdir(ss.str().c_str()) != -1, "Remove directory " << ss.str().c_str());
 TEST_END()
blob - b5b7f57ed4ca0a2f5dd7b6298a98b2c28431e105
blob + 7f087caa76e95ee647bee77a1c0d230aad3f151a
--- vostok/gemini.cc
+++ vostok/gemini.cc
@@ -12,6 +12,7 @@ const std::array<const char, 2> CRLF{'\r', '\n'};
 const std::array<const char, 1> SPACE{' '};
 
 const Status STATUS_20_SUCCESS{'2', '0'};
+const Status STATUS_31_REDIRECT_PERMANENT{'3', '1'};
 const Status STATUS_40_TEMPORARY_FAILURE{'4', '0'};
 const Status STATUS_50_PERMANENT_FAILURE{'5', '0'};
 const Status STATUS_51_NOT_FOUND{'5', '1'};
blob - b2961666037e48305f30dad7d0e7aec54c6ab38f
blob + cc773ff8a5e870b10d30c9be82bc14f75c33ec24
--- vostok/gemini.h
+++ vostok/gemini.h
@@ -19,6 +19,7 @@ extern const std::array<const char, 1> SPACE;
 
 using Status = std::array<const char, 2>;
 extern const Status STATUS_20_SUCCESS;
+extern const Status STATUS_31_REDIRECT_PERMANENT;
 extern const Status STATUS_40_TEMPORARY_FAILURE;
 extern const Status STATUS_50_PERMANENT_FAILURE;
 extern const Status STATUS_51_NOT_FOUND;
blob - 6b9722c01a9c7d891f6f144c06294b9746b0f490
blob + 446f09e8e1c185b1f0dfc5b60b0217c4db190f41
--- vostok/open_file.cc
+++ vostok/open_file.cc
@@ -9,11 +9,12 @@
 
 namespace vostok
 {
-
 namespace open_file
 {
+namespace
+{
 
-static
+
 const char *get_pathname(const std::string &path)
 {
     if (path.empty() || (path.size() == 1 && path[0] == '/'))
@@ -23,6 +24,10 @@ const char *get_pathname(const std::string &path)
     return path.c_str();
 }
 
+
+}   // namespace <unnamed>
+
+
 Result open(
     /* in */ int directory_fd,
     /* in */ const std::string &path,
@@ -40,7 +45,7 @@ Result open(
             },
             error::Print{error_code}
         );
-        return (error_code == ENOENT) ? NOT_FOUND : OPENING_ERROR;
+        return (error_code == ENOENT) ? ERROR_NOT_EXIST : ERROR;
     }
     struct stat sb{};
     if (fstat(opened_fd.get(), &sb) == -1)
@@ -52,62 +57,11 @@ Result open(
             },
             error::Print{}
         );
-        return OPENING_ERROR;
+        return ERROR;
     }
-    return S_ISDIR(sb.st_mode) ? OPENED_DIRECTORY : OPENED;
+    return S_ISDIR(sb.st_mode) ? SUCCESS_DIRECTORY : SUCCESS_FILE;
 }
 
 
 }   // namespace open_file
-
-
-namespace open_directory
-{
-
-
-const std::string g_index_gmi{"index.gmi"};
-
-
-Result open(
-    /* in */ int directory_fd,
-    /* out */ UniqueFd &opened_fd
-)
-{
-    opened_fd.reset(openat(directory_fd, g_index_gmi.c_str(), O_RDONLY));
-    if (!opened_fd)
-    {
-        const auto error_code = errno;
-        error::occurred(
-            []
-            {
-                error::g_log << "Open file \"" << g_index_gmi << "\"";
-            },
-            error::Print{error_code}
-        );
-        return (error_code == ENOENT) ? NOT_FOUND : OPENING_ERROR;
-    }
-    struct stat sb{};
-    if (fstat(opened_fd.get(), &sb) == -1)
-    {
-        error::occurred(
-            []
-            {
-                error::g_log << "Stat file \"" << g_index_gmi << "\"";
-            },
-            error::Print{}
-        );
-        return OPENING_ERROR;
-    }
-    if (S_ISDIR(sb.st_mode))
-    {
-        opened_fd.reset();
-        return NOT_FOUND;
-    }
-    return OPENED;
-}
-
-
-}   // namespace open_directory
-
-
 }   // namespace vostok
blob - 1b9e6dcf59466e29a5c2269d3c64da36ed8f34c2
blob + 65b466d343ad662d961706df5f678d590d7e0bfd
--- vostok/open_file.h
+++ vostok/open_file.h
@@ -8,18 +8,17 @@
 
 namespace vostok
 {
-
 namespace open_file
 {
 
 
 enum Result
 {
-    OPENED,
-    OPENED_DIRECTORY,
+    SUCCESS_FILE,
+    SUCCESS_DIRECTORY,
 
-    NOT_FOUND,
-    OPENING_ERROR,
+    ERROR_NOT_EXIST,
+    ERROR,
 };
 
 Result open(
@@ -30,28 +29,4 @@ Result open(
 
 
 }   // namespace open_file
-
-
-namespace open_directory
-{
-
-
-enum Result
-{
-    OPENED,
-
-    NOT_FOUND,
-    OPENING_ERROR,
-};
-
-Result open(
-    /* in */ int directory_fd,
-    /* out */ UniqueFd &opened_fd
-);
-
-extern const std::string g_index_gmi;
-
-
-}   // namespace open_directory
-
 }   // namespace vostok
blob - df7bf0dab186cee47db6271b9a9a8be91a51a3b5
blob + 5523241a2da909a0eb343c9dcfcf1bfc999b9e49
--- vostok/vostok.cc
+++ vostok/vostok.cc
@@ -30,9 +30,12 @@ const std::string NON_GEMINI{"No proxying to non-Gemin
 const std::string ROOT_TRAVERSE{"Wrong traverse"};
 const std::string NOT_FOUND{"Not found"};
 const std::string TEMPORARY_FAILURE{"Temporary failure"};
+const std::string ROOT{"/"};
 }   // namespace meta
 
+const std::string g_index_gmi{"index.gmi"};
 
+
 bool send_response(NotNull<struct tls *> ctx, gemini::Status status, const std::string &meta)
 {
     // <STATUS><SPACE><META><CR><LF>
@@ -86,40 +89,71 @@ void client_thread(const transport::AcceptedClient *ac
         break;
     }
 
-    OpenedFile opened_file;
-    const auto open_file_result = opened_file.open(directory_fd, path);
+    UniqueFd opened_dir;
+    UniqueFd opened_fd;
+    auto open_file_result = open_file::open(directory_fd, path, opened_fd);
+    const std::string *opened_path{nullptr};
     switch (open_file_result)
     {
-    case OpenedFile::NOT_FOUND:
+    case open_file::ERROR_NOT_EXIST:
         send_response(accepted_client->get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::NOT_FOUND);
         return;
-    case OpenedFile::OPENING_ERROR:
+    case open_file::ERROR:
         send_response(accepted_client->get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::TEMPORARY_FAILURE);
         return;
-
-    case OpenedFile::OPENED:
+    case open_file::SUCCESS_DIRECTORY:
+        if (path.empty())
+        {
+            send_response(accepted_client->get_ctx(), gemini::STATUS_31_REDIRECT_PERMANENT, meta::ROOT);
+            error::g_log << "31 " <<  " -> \"" << meta::ROOT << "\"" << std::endl;
+            return;
+        }
+        if (*path.crbegin() != '/')
+        {
+            assert(path[0] != '/');
+            path.insert(0, 1, '/');
+            path.push_back('/');
+            send_response(accepted_client->get_ctx(), gemini::STATUS_31_REDIRECT_PERMANENT, path);
+            error::g_log << "31 " <<  " -> \"" << path << "\"" << std::endl;
+            return;
+        }
+        opened_dir.reset(opened_fd.release());
+        open_file_result = open_file::open(opened_dir.get(), g_index_gmi, opened_fd);
+        switch(open_file_result)
+        {
+        case open_file::ERROR_NOT_EXIST:
+        case open_file::SUCCESS_DIRECTORY:
+            send_response(accepted_client->get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::NOT_FOUND);
+            return;
+        case open_file::ERROR:
+            send_response(accepted_client->get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::TEMPORARY_FAILURE);
+            return;
+        case open_file::SUCCESS_FILE:
+            opened_path = &g_index_gmi;
+            break;
+        }
         break;
+    case open_file::SUCCESS_FILE:
+        opened_path = &path;
+        break;
     }
 
     // > If <META> is an empty string, the MIME type MUST default to "text/gemini; charset=utf-8".
-    const auto mime_type = mime.lookup(opened_file.get_path());
-    send_response(
-        accepted_client->get_ctx(),
-        gemini::STATUS_20_SUCCESS,
-        mime_type ? *mime_type : meta::_EMPTY
-    );
+    const auto _mime_type = mime.lookup(*opened_path);
+    const std::string &meta_string = _mime_type ? *_mime_type : meta::_EMPTY;
+    send_response(accepted_client->get_ctx(), gemini::STATUS_20_SUCCESS, meta_string);
 
     std::vector<char> buffer;
     buffer.resize(64 * 1024);
     for (; ; )
     {
-        const auto ret = read(opened_file.get_fd(), buffer.data(), buffer.size());
+        const auto ret = read(opened_fd.get(), buffer.data(), buffer.size());
         if (ret == -1)
         {
             error::occurred(
-                [&opened_file]
+                [&path]
                 {
-                    error::g_log << "Read file \"" << opened_file.get_path() << "\"";
+                    error::g_log << "Read file \"" << path << "\"";
                 },
                 error::Print{}
             );
@@ -134,7 +168,7 @@ void client_thread(const transport::AcceptedClient *ac
         if (readed < buffer.size())
             break;
     }
-    error::g_log << "20 " << (mime_type ? *mime_type : meta::_EMPTY) << " \"" << opened_file.get_path() << "\"" << std::endl;
+    error::g_log << "20 " << meta_string << " \"" << path << "\"" << std::endl;
 }