commit - 19ed715d035acf481eb350478d486262d99314b2
commit + e0af76a7865fe55de80790d4129969c206394b58
blob - 28e9c68f37b47c27910971722aaf6032a9f213d6
blob + 5fc78a21d19735dd5d9eaa422635b667b3f1f2d7
--- .gitignore
+++ .gitignore
syntax: glob
cert/
vostok/vostok
-tests/test_request
-tests/test_open_file
+tests/*.test
**/*.swp
**/*.o
blob - 69bd50cfc1aecd54f40ab90568eea2c82144fb52
blob + 6800f1b3e5a99f6cf38f3a3471d80983148f3e73
--- Makefile
+++ Makefile
-.PHONY: server clean run_server tests install
+.PHONY: server clean run tests install
server:
${MAKE} -C vostok
${MAKE} -C vostok clean
${MAKE} -C tests clean
-run_server: server
+run: server
./vostok/vostok -c cert/server.crt -k cert/server.key -f ./
tests:
blob - 0f3019466b5fcf8c9cc286c34515fa9ff67f0576
blob + 8fdc131294e9c9385f9a5f0e5a3691200b834325
--- tests/Makefile
+++ tests/Makefile
.PHONY: tests clean
-CXXFLAGS = -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG -DDEBUG -D_DEBUG -I../vostok
+CXXFLAGS = -DDEBUG -D_DEBUG -D_LIBCPP_DEBUG=1 -D_GLIBCXX_DEBUG -I../vostok
-tests: test_request test_open_file
- ./test_request
- ./test_open_file
+tests: request.test open_file.test mime.test
+ ./request.test
+ ./open_file.test
+ ./mime.test
-test_request: test_request.cc ../vostok/request.cc ../vostok/request.h ../vostok/gemini.cc
- ${CXX} ${CXXFLAGS} test_request.cc ../vostok/request.cc ../vostok/gemini.cc -o test_request
+request.test: test_request.cc ../vostok/request.cc ../vostok/request.h ../vostok/gemini.cc
+ ${CXX} ${CXXFLAGS} test_request.cc ../vostok/request.cc ../vostok/gemini.cc -o request.test
-test_open_file: test_open_file.cc ../vostok/open_file.cc ../vostok/open_file.h
- ${CXX} ${CXXFLAGS} test_open_file.cc ../vostok/open_file.cc -o test_open_file
+open_file.test: test_open_file.cc ../vostok/open_file.cc ../vostok/open_file.h
+ ${CXX} ${CXXFLAGS} test_open_file.cc ../vostok/open_file.cc -o open_file.test
+mime.test: test_mime.cc ../vostok/mime.cc ../vostok/mime.h
+ ${CXX} ${CXXFLAGS} test_mime.cc ../vostok/mime.cc -o mime.test
+
clean:
- rm -f test_request test_open_file
+ rm -f *.test
blob - 490a7207b488c0f90940fda93e95a002abeb7a66
blob + 9417da2e6457b779766d062cf311ab409ea719fd
--- tests/test_open_file.cc
+++ tests/test_open_file.cc
#include <string.h>
-#include <iostream>
#include <sstream>
#include <time.h>
#include <unistd.h>
{
std::ostream dev_null{nullptr};
std::ostream &g_log = dev_null;
-}
-namespace
-{
-template <std::size_t N>
-void fill(std::vector<char> &v, const char (&arr)[N])
-{
- v.resize(N);
- std::copy(&arr[0], &arr[N], v.begin());
-}
-
-template <std::size_t N>
-bool is_tail(Span<const char> opened_path, const char (&arr)[N])
-{
- if (opened_path.size() >= (N - 1))
- {
- opened_path = opened_path.subspan(opened_path.size() - (N - 1));
- return std::equal(opened_path.begin(), opened_path.end(), &arr[0]);
- }
- return false;
-}
-
} // namespace <unnamed>
TEST_START(test_open_file)
IS_TRUE_ERRNO(dir, "Open directory " << ss.str().c_str());
- std::vector<char> sz_file_name;
- Span<const char> opened_path;
+ std::string file_name;
+
{
UniqueFd opened_file;
+ const std::string *opened_path = nullptr;
- fill(sz_file_name, "");
- IS_TRUE(open_file(dir.get(), sz_file_name , opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
+ file_name = "";
+ IS_TRUE(open_file(dir.get(), file_name , opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
- fill(sz_file_name, "non-existent-file");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
+ file_name = "non-existent-file";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
- fill(sz_file_name, "non-existent-dir/file");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
+ file_name = "non-existent-dir/file";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
}
const UniqueFd index_gmi{openat(dir.get(), "index.gmi", O_RDONLY | O_CREAT | O_EXCL, S_IRWXU)};
{
UniqueFd opened_file;
- fill(sz_file_name, "");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
- IS_TRUE(is_tail(opened_path, "index.gmi"));
+ const std::string *opened_path = nullptr;
+ file_name = "";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
+ IS_TRUE(*opened_path == "index.gmi");
+
struct stat sb2{};
IS_TRUE_ERRNO(fstat(opened_file.get(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/index.gmi");
IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
{
UniqueFd opened_file;
- fill(sz_file_name, "index.gmi");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
- IS_TRUE(is_tail(opened_path, "index.gmi"));
+ const std::string *opened_path = nullptr;
+ file_name = "index.gmi";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
+ IS_TRUE(*opened_path == "index.gmi");
struct stat sb2{};
IS_TRUE_ERRNO(fstat(opened_file.get(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/index.gmi");
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");
{
UniqueFd opened_file;
- fill(sz_file_name, "subdir");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
- fill(sz_file_name, "subdir/");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
+ const std::string *opened_path = nullptr;
+
+ file_name = "subdir";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
+
+ file_name = "subdir/";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_NOT_FOUND and !opened_file);
}
const UniqueFd subdir{openat(dir.get(), "subdir", O_RDONLY | O_DIRECTORY)};
IS_TRUE_ERRNO(subdir, "Open directory " << ss.str().c_str() << "/subdir");
{
UniqueFd opened_file;
- fill(sz_file_name, "subdir");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
- IS_TRUE(is_tail(opened_path, "index.gmi"));
+ const std::string *opened_path = nullptr;
+
+ file_name = "subdir";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
+ IS_TRUE(*opened_path == "index.gmi");
struct stat sb2{};
IS_TRUE_ERRNO(fstat(opened_file.get(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
{
UniqueFd opened_file;
- fill(sz_file_name, "subdir/");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
- IS_TRUE(is_tail(opened_path, "index.gmi"));
+ const std::string *opened_path = nullptr;
+
+ file_name = "subdir/";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
+ IS_TRUE(*opened_path == "index.gmi");
struct stat sb2{};
IS_TRUE_ERRNO(fstat(opened_file.get(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
{
UniqueFd opened_file;
- fill(sz_file_name, "subdir/index.gmi");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
- IS_TRUE(is_tail(opened_path, "index.gmi"));
+ const std::string *opened_path = nullptr;
+
+ file_name = "subdir/index.gmi";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENED and opened_file);
+ IS_TRUE(*opened_path == "subdir/index.gmi");
struct stat sb2{};
IS_TRUE_ERRNO(fstat(opened_file.get(), &sb2) != -1, "Stat file " << ss.str().c_str() << "/subdir/index.gmi");
IS_TRUE((sb1.st_dev == sb2.st_dev) && (sb1.st_ino == sb2.st_ino));
{
UniqueFd opened_file;
- fill(sz_file_name, "subdir/EPERM.gmi");
- IS_TRUE(open_file(dir.get(), sz_file_name, opened_file, opened_path) == FILE_OPENING_ERROR and !opened_file);
+ const std::string *opened_path = nullptr;
+
+ file_name = "subdir/EPERM.gmi";
+ IS_TRUE(open_file(dir.get(), file_name, opened_file, opened_path) == FILE_OPENING_ERROR and !opened_file);
}
IS_TRUE_ERRNO(unlinkat(subdir.get(), "EPERM.gmi", 0) != -1, "Remove file " << ss.str().c_str() << "/subdir/EPERM.gmi");
blob - /dev/null
blob + 77c955e6b386a8abd0811a768c9b61eaaf01d43e (mode 644)
--- /dev/null
+++ tests/test_mime.cc
+#include "mime.h"
+#include <cassert>
+
+#include "tests.h"
+
+
+namespace vostok
+{
+
+
+TEST_START(test_mime_get_ext)
+ std::string path{""};
+ auto it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "a";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "ab";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = ".";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "/";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "/.";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "/a";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = "/ab";
+ it = Mime::get_ext(path);
+ IS_TRUE(it == path.end());
+
+ path = ".ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = "a.ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = "ab.ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = ".ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+
+ path = "a.ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+
+ path = "ab.ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+
+ path = "xyz/.ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = "xyz/a.ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = "xyz/ab.ext";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext");
+
+ path = "xyz/.ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+
+ path = "xyz/a.ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+
+ path = "xyz/ab.ext1.ext2";
+ it = Mime::get_ext(path);
+ IS_TRUE(it != path.end() && std::string(&*it) == "ext2");
+TEST_END()
+
+
+TEST_START(test_mime_db)
+ Mime mime;
+
+ IS_TRUE(mime.parse_db("test_mime_types"));
+
+ auto ret = mime.lookup(".unk");
+ IS_TRUE(ret == nullptr);
+
+ ret = mime.lookup(".rss");
+ IS_TRUE(ret == nullptr);
+
+ ret = mime.lookup(".pd");
+ IS_TRUE(ret == nullptr);
+ ret = mime.lookup(".df");
+ IS_TRUE(ret == nullptr);
+ ret = mime.lookup(".pf");
+ IS_TRUE(ret == nullptr);
+
+ ret = mime.lookup(".pdf");
+ IS_TRUE(ret != nullptr && *ret == "application/pdf");
+
+ ret = mime.lookup(".ps");
+ IS_TRUE(ret != nullptr && *ret == "application/postscript");
+ ret = mime.lookup(".eps");
+ IS_TRUE(ret != nullptr && *ret == "application/postscript");
+ ret = mime.lookup(".ai");
+ IS_TRUE(ret != nullptr && *ret == "application/postscript");
+
+ ret = mime.lookup(".rtf");
+ IS_TRUE(ret != nullptr && *ret == "application/rtf");
+TEST_END()
+
+
+} // namespace vostok
+
+
+extern "C"
+int
+main(int, char **)
+{
+ const auto ret = vostok::test_mime_get_ext();
+ return ret ? ret : vostok::test_mime_db();
+}
blob - 7a61653e524b22ec02a4d5d321f6ade1014aeb3e
blob + 8b266eeb9ade6abe27c9aba7f8dd16369f028e4f
--- tests/test_request.cc
+++ tests/test_request.cc
#include "request.h"
-#include <string.h>
-#include <iostream>
+#include <cassert>
#include "tests.h"
namespace
{
template <std::size_t N>
-void fill_request_buffer(std::vector<char> &v, const char (&arr)[N])
+std::string &fill_request_buffer(Request &r, const char (&arr)[N])
{
static_assert(N > 0, "N");
- v.resize(N + 1);
- auto p = std::copy(&arr[0], &arr[N - 1], v.begin());
+ auto &s = r.get_buffer();
+ assert(s.size() >= (N + 1));
+ auto p = std::copy(&arr[0], &arr[N - 1], s.begin());
*p = '\r';
++p;
*p = '\n';
++p;
+ s.resize(N + 1);
+ return s;
}
} // namespace <unnamed>
TEST_START(test_request)
Request request;
+ std::string path;
- auto &buffer = request.get_buffer();
- IS_TRUE(buffer.size() == 1026);
+ {
+ auto &buffer = request.get_buffer();
+ IS_TRUE(buffer.size() == 1026);
+ }
- fill_request_buffer(buffer, "gemini://host");
- buffer[buffer.size() - 1] = '\0';
- IS_TRUE(request.parse() == Request::BAD_REQUEST);
+ {
+ auto &buffer = fill_request_buffer(request, "gemini://host");
+ buffer[buffer.size() - 1] = '\0';
+ IS_TRUE(request.parse(path) == Request::BAD_REQUEST);
+ }
#define CASE_ERROR(url_literal, expected_result) \
- fill_request_buffer(request.get_buffer(), url_literal); \
- IS_TRUE(request.parse() == expected_result)
+ fill_request_buffer(request, url_literal); \
+ IS_TRUE(request.parse(path) == expected_result)
CASE_ERROR("", Request::URL_TOO_SHORT);
CASE_ERROR("g", Request::URL_TOO_SHORT);
CASE_ERROR("gemini://host/dir/../../secret.txt", Request::URL_ROOT_TRAVERSE);
#define CASE_OK(url_literal, path_literal) \
- fill_request_buffer(request.get_buffer(), url_literal); \
- IS_TRUE(request.parse() == Request::URL_OK && !strcmp(request.get_path().data(), path_literal))
+ fill_request_buffer(request, url_literal); \
+ IS_TRUE(request.parse(path) == Request::URL_OK); \
+ IS_TRUE(path == path_literal)
CASE_OK("gemini://host", "");
CASE_OK("gemini://host/", "");
blob - /dev/null
blob + 2437b1e2043e837bb56255a59b6de6e9011d3e06 (mode 644)
--- /dev/null
+++ tests/test_mime_types
+#application/rss+xml rss
+
+application/pdf pdf
+
+
+application/postscript ps eps ai
+
+application/rtf rtf
blob - f63e7b699fe5770773ae17075b21503eec03a33c
blob + e90e0c9567f320b98dda8251cc83251d8be4963a
--- tests/tests.h
+++ tests/tests.h
+#include <iostream>
+
#pragma once
#define TEST_START(name) int name() {
blob - 65f8d32addaca2f9201a5b5e06104c92c6b90769
blob + 9033a5cc11774e198b77ab33da9e8b3c988130ab
--- vostok/Makefile
+++ vostok/Makefile
CXXFLAGS += -Wall -Wextra -std=c++11
LIBS = -ltls
-CXXFILES = transport.cc
-CXXFILES += error.cc
-CXXFILES += gemini.cc
-CXXFILES += args.cc
-CXXFILES += request.cc
-CXXFILES += open_file.cc
-CXXFILES += vostok.cc
+CXXFILES = transport.cc error.cc gemini.cc args.cc request.cc open_file.cc mime.cc vostok.cc
+HXXFILES = args.h error.h gemini.h mime.h open_file.h request.h transport.h utils.h
OFILES = ${CXXFILES:.cc=.o}
-.cc.o:
+.cc.o: ${HXXFILES}
${CXX} ${CXXFLAGS} -c -o $@ $<
vostok: ${OFILES}
blob - 19144e4a70be55173bbd017c1697f77ebf2669bc
blob + 1f1e2b7aecd1bb638f36b78758a34a75065213d9
--- vostok/args.cc
+++ vostok/args.cc
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-f PATH : Path to file system data [REQUIRED]" << std::endl;
+ error::g_log << "\t-m FILE : Path to file mime.types" << std::endl;
return false;
}
{
int ch;
char *p = nullptr;
- while ((ch = getopt(argc, argv, "a:p:c:k:f:")) != -1) {
+ while ((ch = getopt(argc, argv, "a:p:c:k:f:m:")) != -1) {
switch (ch) {
case 'a':
args.addr = optarg;
return false;
}
break;
+ case 'm':
+ if (!args.mime.parse_db(optarg))
+ return false;
+ break;
default:
return usage(argv[0]);
blob - 0d5d970247a1b2947a8fb3f7fb034b373f784656
blob + a9a2b6a44858304b3e76f8adca2f76bcc73105fb
--- vostok/args.h
+++ vostok/args.h
/** Parse command line arguments */
#include "utils.h"
+#include "mime.h"
#pragma once
czstring cert_file{nullptr};
czstring key_file{nullptr};
UniqueFd directory;
+ Mime mime;
};
blob - bf7fc548d06c4122ff18d59004d8010ca6c8636c
blob + 7e53ac30ac83be704bad5b3eb71ab4cf0ab4c44e
--- vostok/open_file.cc
+++ vostok/open_file.cc
namespace vostok
{
-namespace
-{
-const char index_gmi[] = "index.gmi";
-} // namespace <unnamed>
+static const std::string index_gmi{"index.gmi"};
OpenFileResult
open_file(
/* in */ int directory_fd,
- /* in */ const std::vector<char> &sz_file_name,
+ /* in */ const std::string &request_path,
/* out */ UniqueFd &opened_file,
- /* out */ Span<const char> &opened_path
+ /* out */ const std::string* &opened_path
)
{
- assert(sz_file_name.size());
-
- NotNull<czstring> file_name = sz_file_name.data();
- opened_path = cut_null(sz_file_name);
-
- if (!*file_name)
- {
- file_name = index_gmi;
- opened_path = cut_null(index_gmi);
- }
-
- opened_file.reset(openat(directory_fd, file_name, O_RDONLY));
+ opened_path = request_path.empty() ? &index_gmi : &request_path;
+ opened_file.reset(openat(directory_fd, opened_path->c_str(), O_RDONLY));
if (!opened_file)
{
const auto error_code = errno;
error::occurred(
- [file_name]
+ [&opened_path]
{
- error::g_log << "Open file \"" << file_name << "\"";
+ error::g_log << "Open file \"" << opened_path->c_str() << "\"";
},
error::Print{error_code}
);
if (fstat(opened_file.get(), &sb) == -1)
{
error::occurred(
- [file_name]
+ [&opened_path]
{
- error::g_log << "Stat file \"" << file_name << "\"";
+ error::g_log << "Stat file \"" << opened_path->c_str() << "\"";
},
error::Print{}
);
if (S_ISDIR(sb.st_mode))
{
const UniqueFd new_parent{opened_file.release()};
- opened_path = cut_null(index_gmi);
- opened_file.reset(openat(new_parent.get(), index_gmi, O_RDONLY));
+ opened_path = &index_gmi;
+ opened_file.reset(openat(new_parent.get(), opened_path->c_str(), O_RDONLY));
if (!opened_file)
{
const auto error_code = errno;
error::occurred(
- [file_name]
+ [&opened_path]
{
- error::g_log << "Open file \"" << file_name << "\" /" << index_gmi;
+ error::g_log << "Open file \"" << opened_path->c_str() << "\" /" << index_gmi;
},
error::Print{error_code}
);
blob - /dev/null
blob + 80a02c2b1fc22087cef8d3252cc8cb4c67b82f37 (mode 644)
--- /dev/null
+++ vostok/mime.cc
+/* Detect MIME file type */
+
+#include "mime.h"
+
+#include <fstream>
+#include <sstream>
+
+
+namespace vostok
+{
+
+
+bool Mime::parse_db(NotNull<czstring> sz_path)
+{
+ std::ifstream ifstream;
+ ifstream.open(sz_path);
+
+ for (std::string line; std::getline(ifstream, line); )
+ {
+ if (line.empty())
+ continue;
+ if (line[0] == '#')
+ continue;
+
+ std::istringstream line_stream(line);
+ std::string type;
+ for (std::string token; std::getline(line_stream, token, '\t'); )
+ {
+ if (token.empty())
+ continue;
+ if (type.empty())
+ {
+ type = token;
+ continue;
+ }
+
+ std::istringstream ext_stream(token);
+ for (std::string ext; std::getline(ext_stream, ext, ' '); )
+ {
+ if (!ext.empty())
+ {
+ Key key{ext};
+ m_map.emplace(std::move(key), type);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+const std::string *Mime::lookup(const std::string &path) const
+{
+ const auto it = get_ext(path);
+ if (it == path.end())
+ return nullptr;
+
+ auto found = m_map.find(Key{&*it});
+ return found == m_map.end() ? nullptr : &(found->second);
+}
+
+
+std::string::const_iterator Mime::get_ext(const std::string &path)
+{
+ if (!path.empty())
+ {
+ auto current = (path.end() - 1);
+ for (; ; )
+ {
+ if (*current == '/')
+ break;
+
+ if (*current != '.')
+ {
+ if (current == path.begin())
+ break;
+
+ --current;
+ continue;
+ }
+ return current + 1;
+ }
+ }
+ return path.end();
+}
+
+
+bool Mime::Less::operator() (const Mime::Key &key1, const Mime::Key &key2) const
+{
+ return strcasecmp(key1.c_str(), key2.c_str()) < 0;
+}
+
+
+} // namespace vostok
blob - a69a53ff70dbd260bb80d14e0faa7e9017c6584a
blob + 3b9c2eb7ae75aafc8380763b767c4323b8f75ee5
--- vostok/open_file.h
+++ vostok/open_file.h
/** Open file for Gemini response */
#include "utils.h"
+#include <string>
#pragma once
namespace vostok
{
-
enum OpenFileResult
{
FILE_OPENED,
OpenFileResult
open_file(
/* in */ int directory_fd,
- /* in */ const std::vector<char> &sz_file_name,
+ /* in */ const std::string &request_path,
/* out */ UniqueFd &opened_file,
- /* out */ Span<const char> &opened_path
+ /* out */ const std::string* &opened_path
);
blob - /dev/null
blob + d090a2aee0616e7839bf45500d48bda520a7d7da (mode 644)
--- /dev/null
+++ vostok/mime.h
+/* Detect MIME file type */
+
+#include "utils.h"
+
+#include <string>
+#include <map>
+
+#pragma once
+
+
+namespace vostok
+{
+
+
+class Mime
+{
+public:
+ /* Read and parse "mime.types" file content */
+ bool parse_db(NotNull<czstring> sz_path);
+
+ /* Lookup MIME type by FS path */
+ const std::string *lookup(const std::string &path) const;
+
+ /* Get file extension from path. Return path.end() if no extension. */
+ static std::string::const_iterator get_ext(const std::string &path);
+
+protected:
+ class Key
+ {
+ public:
+ explicit Key(/* in/out */ std::string &string) {m_string.swap(string);}
+ explicit Key(const char *p) : m_p(p) {}
+ const char *c_str() const { return m_p ? m_p : m_string.c_str(); }
+
+ private:
+ std::string m_string;
+ const char *m_p{nullptr};
+ };
+
+private:
+ class Less
+ {
+ public:
+ bool operator ()(const Key &key1, const Key &key2) const;
+ };
+ std::map<Key, std::string, Less> m_map;
+};
+
+
+} // namespace vostok
blob - 7bc242fbd334c6b9df1ffaa6c3a54ad249ca3f5c
blob + e0d4eb17cf214b9012e7bfc4b6191d91444626cd
--- vostok/request.cc
+++ vostok/request.cc
{
const auto gemini_scheme = cut_null("gemini://");
-inline bool is_gemini_scheme(const std::vector<char> &url)
+inline bool is_gemini_scheme(const std::string &url)
{
return std::equal(
gemini_scheme.begin(),
}
-bool cut_crlf(std::vector<char> &buffer)
+bool cut_crlf(std::string &buffer)
{
// > servers MUST ignore anything sent after the first occurrence of a <CR><LF>.
for (auto current = buffer.cbegin(); current != buffer.cend(); ++current)
Request::ParseResult
operator() (
- std::vector<char>::const_iterator p,
- std::vector<char> &url
+ std::string::const_iterator p,
+ std::string &url
);
protected:
return Request::URL_OK;
}
- void fill(std::vector<char> &url) const;
+ void fill(std::string &url) const;
private:
Components::value_type m_inprogress;
Request::ParseResult
PathNormalization::operator() (
- std::vector<char>::const_iterator p,
- std::vector<char> &url
+ std::string::const_iterator p,
+ std::string &url
)
{
m_inprogress = Components::value_type{nullptr, 0};
return on_component_ok();
}
-void PathNormalization::fill(std::vector<char> &url) const
+void PathNormalization::fill(std::string &url) const
{
auto current = url.begin();
for (auto it = m_result.cbegin(); it != m_result.cend(); ++it)
}
current = std::copy(it->begin(), it->end(), current);
}
- *current = '\0';
- ++current;
-
url.resize(current - url.begin());
}
} // namespace <unnamed>
-std::vector<char> &Request::get_buffer()
+std::string &Request::get_buffer()
{
m_buffer.resize(gemini::MAX_REQUEST_LENGTH);
return m_buffer;
}
-Request::ParseResult Request::parse()
+Request::ParseResult Request::parse(/* out */ std::string &path)
{
if (!cut_crlf(m_buffer))
return BAD_REQUEST;
}
// normalize '.' and '..'
- return PathNormalization{}(current, m_buffer);
+ const auto ret = PathNormalization{}(current, m_buffer);
+ if (ret == URL_OK)
+ m_buffer.swap(path);
+ return ret;
}
-const std::vector<char> &Request::get_path() const
-{
- assert(m_buffer.size() > 0);
- assert(m_buffer.size() < gemini::MAX_REQUEST_LENGTH);
- assert(m_buffer[m_buffer.size() - 1] == '\0');
- return m_buffer;
-}
-
-
} // namespace vostok
blob - 9beac489676e75d5c1a7deee44be8c08bd0475e9
blob + 1359056f3684043421faf449d2d868ff02af7caa
--- vostok/request.h
+++ vostok/request.h
/** Gemini request parser */
-#include <vector>
+#include <string>
#pragma once
{
public:
/* Get buffer for incoming Gemini request */
- std::vector<char> &get_buffer();
+ std::string &get_buffer();
- /* Parse incoming Gemini request */
+ /* Parse incoming Gemini request
+ and return normalized path as zero-terminated string (if URL_OK) */
enum ParseResult
{
URL_OK,
URL_NON_GEMINI,
URL_ROOT_TRAVERSE,
};
- ParseResult parse();
+ ParseResult parse(/* out */ std::string &path);
-
- /* Get normalized path (if parse() return URL_OK) as zero-terminated string */
- const std::vector<char> &get_path() const;
-
private:
- std::vector<char> m_buffer;
+ std::string m_buffer;
};
blob - ce1c426013b7734b3cb83baba2ea6915037fc386
blob + c7684b15aa5d1d31442d925abd0729a7f80e1e78
--- vostok/transport.cc
+++ vostok/transport.cc
bool recv(
/* in */ NotNull<struct tls *> ctx,
- /* in/out */ std::vector<char> &buff
+ /* in/out */ Span<char> &buff
)
{
ssize_t ret{};
error::occurred("TLS read", PrintIfError{tls_error(ctx)});
return false;
}
- buff.resize(ret);
+ buff = buff.first(ret);
return true;
}
while (buff.size() > 0)
{
ret = tls_write(ctx, buff.begin(), buff.size());
+ error::g_log << "|" << std::dec << ret << " (0x" << std::hex << ret << ")|" << std::endl;
if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
continue;
- break;
- buff = buff.subspan(buff.size() - ret);
+ buff = buff.subspan(ret);
}
if (ret == -1)
{
blob - 08c3def08a75168b755be2465e2b8845f5f517c3
blob + 5533e0ea87859946aed4bfb8a63870bd800eb07a
--- vostok/transport.h
+++ vostok/transport.h
bool
recv(
/* in */ NotNull<struct tls *> ctx,
- /* in/out */ std::vector<char> &buff
+ /* in/out */ Span<char> &buff
);
+template <class Container>
+bool
+recv(
+ /* in */ NotNull<struct tls *> ctx,
+ /* in/out */ Container &buff
+)
+{
+ Span<char> span{buff};
+ const auto ret = recv(ctx, span);
+ if (!ret)
+ return false;
+ buff.resize(span.size());
+ return true;
+}
+
+
/** Send raw bytes */
bool
send(
blob - 60749f41c58b753468d478666c6e0568f18c687b
blob + a112a379cd9c0119277eb500296040c22a07bf0d
--- vostok/utils.h
+++ vostok/utils.h
#include <cstddef>
#include <cassert>
-#include <array>
-#include <vector>
+#include <iterator>
#include <unistd.h>
public:
using element_type = ElemT;
using iterator = ElemT *;
+ using reverse_iterator = std::reverse_iterator<iterator>;
constexpr Span() : m_p{nullptr}, m_count{0} {}
constexpr Span(element_type *p, std::size_t count) : m_p{count ? p : nullptr}, m_count{count} {}
template <std::size_t N>
constexpr Span(element_type (&arr)[N]) : m_p{arr}, m_count{N} {}
- template <std::size_t N>
- constexpr Span(const std::array<element_type, N> &arr) : m_p{arr.data()}, m_count{N} {}
- template <typename other_element_type>
- Span(const std::vector<other_element_type> &v)
- {
- if (v.size())
- {
- m_p = v.data();
- m_count = v.size();
- }
- else
- {
- m_p = nullptr;
- m_count = 0;
- }
- }
+ template <class Container>
+ constexpr Span(Container &c) : m_p{c.size() ? &*c.begin() : nullptr}, m_count{c.size()} {}
constexpr std::size_t size() const {return m_count;}
constexpr iterator begin() const noexcept { return m_p; }
constexpr iterator end() const noexcept { return m_p + size(); }
+ reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); }
+ reverse_iterator rend() const noexcept { return reverse_iterator(begin());}
+
Span<element_type> first(std::size_t count) const
{
assert(count <= m_count);
return (offset < m_count) ? Span<element_type>{m_p + offset, m_count - offset} : Span<element_type>{};
}
+ element_type *data() const { return m_p; }
element_type &operator[](std::size_t idx) const
{
assert(idx < m_count);
static_assert(N > 0, "!(N > 0)");
return Span<const char>{arr, N - 1};
}
-template <typename ElemT>
-Span<const char> cut_null(const std::vector<ElemT> &v)
-{
- assert(v.size() > 0);
- assert(v[v.size() - 1] == '\0');
- return Span<const char>{v.data(), v.size() - 1};
-}
/** Smart pointer for file descriptor */
blob - 35c28e6faddbc3bc9f924773c695981ddadef313
blob + cc2dda64753c42de52c31b28c53ec3385379e310
--- vostok/vostok.cc
+++ vostok/vostok.cc
#include <arpa/inet.h>
#include <netinet/in.h>
+#include <vector>
#include <thread>
namespace meta
{
-const char sz_bad_request[] = "Bad request";
-const auto bad_request = cut_null(sz_bad_request);
-
-const char sz_url_too_short[] = "URL is too short";
-const auto url_too_short = cut_null(sz_url_too_short);
-
-const char sz_non_gemini[] = "No proxying to non-Gemini content";
-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);
+const std::string _EMPTY;
+const std::string BAD_REQUEST{"Bad request"};
+const std::string URL_TOO_SHORT{"URL is too short"};
+const std::string NON_GEMINI{"No proxying to non-Gemini content"};
+const std::string ROOT_TRAVERSE{"Wrong traverse"};
+const std::string NOT_FOUND{"Not found"};
+const std::string TEMPORARY_FAILURE{"Temporary failure"};
} // namespace meta
-bool send_response(NotNull<struct tls *> ctx, gemini::Status status, Span<const char> meta)
+
+bool send_response(NotNull<struct tls *> ctx, gemini::Status status, const std::string &meta)
{
// <STATUS><SPACE><META><CR><LF>
if (!transport::send(ctx, status))
return false;
if (!transport::send(ctx, gemini::SPACE))
return false;
- if (meta.size())
+ if (!meta.empty())
{
if (!transport::send(ctx, meta))
return false;
-void client_thread(const transport::AcceptedClient *accepted_client, int directory_fd)
+void client_thread(const transport::AcceptedClient *accepted_client, int directory_fd, const Mime &mime)
{
assert(accepted_client);
std::unique_ptr<const transport::AcceptedClient> accepted_client_deleter{accepted_client};
if (!transport::recv(accepted_client->get_ctx(), request.get_buffer()))
return;
- const auto parse_result = request.parse();
+ std::string path;
+ const auto parse_result = request.parse(path);
switch (parse_result)
{
case Request::BAD_REQUEST:
- error::occurred("Parse request", []{error::g_log << meta::sz_bad_request;});
- send_response(accepted_client->get_ctx(), gemini::STATUS_59_BAD_REQUEST, meta::bad_request);
+ error::occurred("Parse request", []{error::g_log << meta::BAD_REQUEST;});
+ send_response(accepted_client->get_ctx(), gemini::STATUS_59_BAD_REQUEST, meta::BAD_REQUEST);
return;
case Request::URL_TOO_SHORT:
- error::occurred("Parse request", []{error::g_log << meta::sz_url_too_short;});
- send_response(accepted_client->get_ctx(), gemini::STATUS_59_BAD_REQUEST, meta::url_too_short);
+ error::occurred("Parse request", []{error::g_log << meta::URL_TOO_SHORT;});
+ send_response(accepted_client->get_ctx(), gemini::STATUS_59_BAD_REQUEST, meta::URL_TOO_SHORT);
return;
case Request::URL_NON_GEMINI:
- error::occurred("Parse request", []{error::g_log << meta::sz_non_gemini;});
- send_response(accepted_client->get_ctx(), gemini::STATUS_53_PROXY_REQUEST_REFUSED, meta::non_gemini);
+ error::occurred("Parse request", []{error::g_log << meta::NON_GEMINI;});
+ send_response(accepted_client->get_ctx(), gemini::STATUS_53_PROXY_REQUEST_REFUSED, meta::NON_GEMINI);
return;
case Request::URL_ROOT_TRAVERSE:
- error::occurred("Parse request", []{error::g_log << meta::sz_root_traverse;});
- send_response(accepted_client->get_ctx(), gemini::STATUS_50_PERMANENT_FAILURE, meta::root_traverse);
+ error::occurred("Parse request", []{error::g_log << meta::ROOT_TRAVERSE;});
+ send_response(accepted_client->get_ctx(), gemini::STATUS_50_PERMANENT_FAILURE, meta::ROOT_TRAVERSE);
return;
case Request::URL_OK:
}
UniqueFd opened_file{};
- Span<const char> opened_path;
- const auto open_file_result = open_file(directory_fd, request.get_path(), opened_file, opened_path);
+ const std::string *opened_path = nullptr;
+ const auto open_file_result = open_file(directory_fd, path, opened_file, opened_path);
switch (open_file_result)
{
case FILE_NOT_FOUND:
- send_response(accepted_client->get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::not_found);
+ send_response(accepted_client->get_ctx(), gemini::STATUS_51_NOT_FOUND, meta::NOT_FOUND);
return;
case FILE_OPENING_ERROR:
- send_response(accepted_client->get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::temporary_failure);
+ send_response(accepted_client->get_ctx(), gemini::STATUS_40_TEMPORARY_FAILURE, meta::TEMPORARY_FAILURE);
return;
case FILE_OPENED:
- assert(opened_file);
+ assert(opened_file && opened_path);
break;
}
// > If <META> is an empty string, the MIME type MUST default to "text/gemini; charset=utf-8".
- send_response(accepted_client->get_ctx(), gemini::STATUS_20_SUCCESS, {});
+ const auto mime_type = mime.lookup(*opened_path);
+ send_response(
+ accepted_client->get_ctx(),
+ gemini::STATUS_20_SUCCESS,
+ mime_type ? *mime_type : meta::_EMPTY
+ );
std::vector<char> buffer;
buffer.resize(64 * 1024);
if (ret == -1)
{
error::occurred(
- [&request]
+ [&path]
{
- error::g_log << "Read file \"" << request.get_path().data() << "\"";
+ error::g_log << "Read file \"" << path.c_str() << "\"";
},
error::Print{}
);
if (readed < buffer.size())
break;
}
- error::g_log << "20 " << "\"" << request.get_path().data() << "\"" << std::endl;
+ error::g_log << "20 " << (mime_type ? *mime_type : meta::_EMPTY) << " \"" << path.c_str() << "\"" << std::endl;
}
-bool server_loop(int server_socket, NotNull<struct tls *>server_ctx, int directory_fd)
+bool server_loop(int server_socket, NotNull<struct tls *>server_ctx, int directory_fd, const Mime &mime)
{
error::g_log << "🚀 Vostok server listening..." << std::endl;
for (; ; )
{
try
{
- std::thread{client_thread, accepted_client.get(), directory_fd}.detach();
+ std::thread{client_thread, accepted_client.get(), directory_fd, mime}.detach();
}
catch (const std::system_error &e)
{
return false;
}
- return server_loop(server_socket.get(), server_ctx.get(), args.directory.get());
+ return server_loop(server_socket.get(), server_ctx.get(), args.directory.get(), args.mime);
}