Archived
1
0

more nntpchan-daemon stuff

This commit is contained in:
Jeff Becker
2017-10-11 09:48:27 -04:00
parent ec7a17a647
commit 12bb8c4936
66 changed files with 1061 additions and 119 deletions

View File

@@ -1,4 +1,4 @@
#include "base64.hpp"
#include <nntpchan/base64.hpp>
// taken from i2pd

View File

@@ -1,17 +0,0 @@
#ifndef NNTPCHAN_BASE64_HPP
#define NNTPCHAN_BASE64_HPP
#include <string>
#include <vector>
namespace nntpchan
{
/** returns base64 encoded string */
std::string B64Encode(const uint8_t * data, const std::size_t l);
/** @brief returns true if decode was successful */
bool B64Decode(const std::string & data, std::vector<uint8_t> & out);
}
#endif

View File

@@ -1,4 +1,4 @@
#include "buffer.hpp"
#include <nntpchan/buffer.hpp>
#include <cstring>
namespace nntpchan
@@ -9,7 +9,7 @@ namespace nntpchan
std::memcpy(buf, b, s);
this->b = uv_buf_init(buf, s);
w.data = this;
};
}
WriteBuffer::WriteBuffer(const std::string & s) : WriteBuffer(s.c_str(), s.size()) {}

View File

@@ -1,19 +0,0 @@
#ifndef NNTPCHAN_BUFFER_HPP
#define NNTPCHAN_BUFFER_HPP
#include <uv.h>
#include <string>
namespace nntpchan
{
struct WriteBuffer
{
uv_write_t w;
uv_buf_t b;
WriteBuffer(const std::string & s);
WriteBuffer(const char * b, const size_t s);
~WriteBuffer();
};
}
#endif

View File

@@ -1,4 +1,4 @@
#include "crypto.hpp"
#include <nntpchan/crypto.hpp>
#include <sodium.h>
#include <cassert>

View File

@@ -1,22 +0,0 @@
#ifndef NNTPCHAN_CRYPTO_HPP
#define NNTPCHAN_CRYPTO_HPP
#include <sodium/crypto_hash.h>
#include <array>
namespace nntpchan
{
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
void SHA512(const uint8_t * d, std::size_t l, SHA512Digest & h);
/** global crypto initializer */
struct Crypto
{
Crypto();
~Crypto();
};
}
#endif

View File

@@ -1,4 +1,4 @@
#include "event.hpp"
#include <nntpchan/event.hpp>
#include <cassert>
namespace nntpchan

View File

@@ -1,26 +0,0 @@
#ifndef NNTPCHAN_EVENT_HPP
#define NNTPCHAN_EVENT_HPP
#include <uv.h>
namespace nntpchan
{
class Mainloop
{
public:
Mainloop();
~Mainloop();
operator uv_loop_t * () const { return m_loop; }
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
void Stop();
private:
uv_loop_t * m_loop;
};
}
#endif

View File

@@ -1,4 +1,4 @@
#include "exec_frontend.hpp"
#include <nntpchan/exec_frontend.hpp>
#include <cstring>
#include <iostream>
#include <errno.h>

View File

@@ -1,30 +0,0 @@
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
#define NNTPCHAN_EXEC_FRONTEND_HPP
#include "frontend.hpp"
#include <deque>
namespace nntpchan
{
class ExecFrontend : public Frontend
{
public:
ExecFrontend(const std::string & exe);
~ExecFrontend();
void ProcessNewMessage(const fs::path & fpath);
bool AcceptsNewsgroup(const std::string & newsgroup);
bool AcceptsMessage(const std::string & msgid);
private:
int Exec(std::deque<std::string> args);
private:
std::string m_exec;
};
}
#endif

View File

@@ -1,4 +1,4 @@
#include "file_handle.hpp"
#include <nntpchan/file_handle.hpp>
namespace nntpchan

View File

@@ -1,23 +0,0 @@
#ifndef NNTPCHAN_FILE_HANDLE_HPP
#define NNTPCHAN_FILE_HANDLE_HPP
#include <memory>
#include <fstream>
#include <experimental/filesystem>
namespace nntpchan
{
typedef std::unique_ptr<std::fstream> FileHandle_ptr;
enum FileMode
{
eRead,
eWrite
};
namespace fs = std::experimental::filesystem;
FileHandle_ptr OpenFile(const fs::path & fname, FileMode mode);
}
#endif

View File

@@ -1,29 +0,0 @@
#ifndef NNTPCHAN_FRONTEND_HPP
#define NNTPCHAN_FRONTEND_HPP
#include <string>
#include <memory>
#include <experimental/filesystem>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
/** @brief nntpchan frontend ui interface */
class Frontend
{
public:
/** @brief process an inbound message stored at fpath that we have accepted. */
virtual void ProcessNewMessage(const fs::path & fpath) = 0;
/** @brief return true if we take posts in a newsgroup */
virtual bool AcceptsNewsgroup(const std::string & newsgroup) = 0;
/** @brief return true if we will accept a message given its message-id */
virtual bool AcceptsMessage(const std::string & msgid) = 0;
};
typedef std::unique_ptr<Frontend> Frontend_ptr;
}
#endif

View File

@@ -1,6 +0,0 @@
#ifndef NNTPCHAN_HTTP_HPP
#define NNTPCHAN_HTTP_HPP
#endif

View File

@@ -1,5 +0,0 @@
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
#define NNTPCHAN_HTTP_CLIENT_HPP
#endif

View File

@@ -1,6 +0,0 @@
#ifndef NNTPCHAN_HTTP_SERVER_HPP
#define NNTPCHAN_HTTP_SERVER_HPP
#endif

View File

@@ -1,186 +0,0 @@
/**
* The MIT License (MIT)
* Copyright (c) <2015> <carriez.md@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef INI_HPP
#define INI_HPP
#include <cassert>
#include <map>
#include <list>
#include <stdexcept>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
namespace INI {
struct Level
{
Level() : parent(NULL), depth(0) {}
Level(Level* p) : parent(p), depth(0) {}
typedef std::map<std::string, std::string> value_map_t;
typedef std::map<std::string, Level> section_map_t;
typedef std::list<value_map_t::const_iterator> values_t;
typedef std::list<section_map_t::const_iterator> sections_t;
value_map_t values;
section_map_t sections;
values_t ordered_values; // original order in the ini file
sections_t ordered_sections;
Level* parent;
size_t depth;
const std::string& operator[](const std::string& name) { return values[name]; }
Level& operator()(const std::string& name) { return sections[name]; }
};
class Parser
{
public:
Parser(const char* fn);
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
Level& top() { return top_; }
void dump(std::ostream& s) { dump(s, top(), ""); }
private:
void dump(std::ostream& s, const Level& l, const std::string& sname);
void parse(Level& l);
void parseSLine(std::string& sname, size_t& depth);
void err(const char* s);
private:
Level top_;
std::ifstream f0_;
std::istream* f_;
std::string line_;
size_t ln_;
};
inline void
Parser::err(const char* s)
{
char buf[256];
sprintf(buf, "%s on line #%ld", s, ln_);
throw std::runtime_error(buf);
}
inline std::string trim(const std::string& s)
{
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for (; sp <= ep; ++sp)
if (!strchr(p, s[sp])) break;
for (; ep >= 0; --ep)
if (!strchr(p, s[ep])) break;
return s.substr(sp, ep-sp+1);
}
inline
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
{
if (!f0_)
throw std::runtime_error(std::string("failed to open file: ") + fn);
parse(top_);
}
inline void
Parser::parseSLine(std::string& sname, size_t& depth)
{
depth = 0;
for (; depth < line_.length(); ++depth)
if (line_[depth] != '[') break;
sname = line_.substr(depth, line_.length() - 2*depth);
}
inline void
Parser::parse(Level& l)
{
while (std::getline(*f_, line_)) {
++ln_;
if (line_[0] == '#' || line_[0] == ';') continue;
line_ = trim(line_);
if (line_.empty()) continue;
if (line_[0] == '[') {
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level* lp = NULL;
Level* parent = &l;
if (depth > l.depth + 1)
err("section with wrong depth");
if (l.depth == depth-1)
lp = &l.sections[sname];
else {
lp = l.parent;
size_t n = l.depth - depth;
for (size_t i = 0; i < n; ++i) lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if (lp->depth != 0)
err("duplicate section name on the same level");
if (!lp->parent) {
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
} else {
size_t n = line_.find('=');
if (n == std::string::npos)
err("no '=' found");
std::pair<Level::value_map_t::const_iterator, bool> res =
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
trim(line_.substr(n+1, line_.length()-n-1))));
if (!res.second)
err("duplicated key found");
l.ordered_values.push_back(res.first);
}
}
}
inline void
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
{
if (!sname.empty()) s << '\n';
for (size_t i = 0; i < l.depth; ++i) s << '[';
if (!sname.empty()) s << sname;
for (size_t i = 0; i < l.depth; ++i) s << ']';
if (!sname.empty()) s << std::endl;
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
s << (*it)->first << '=' << (*it)->second << std::endl;
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
assert((*it)->second.depth == l.depth+1);
dump(s, (*it)->second, (*it)->first);
}
}
}
#endif // INI_HPP

View File

@@ -1,12 +0,0 @@
#ifndef NNTPCHAN_IO_HANDLE_HPP
#define NNTPCHAN_IO_HANDLE_HPP
#include <memory>
#include <iostream>
namespace nntpchan
{
typedef std::unique_ptr<std::iostream> IOHandle_ptr;
}
#endif

View File

@@ -1,4 +1,4 @@
#include "line.hpp"
#include <nntpchan/line.hpp>
namespace nntpchan {

View File

@@ -1,34 +0,0 @@
#ifndef NNTPCHAN_LINE_HPP
#define NNTPCHAN_LINE_HPP
#include "server.hpp"
#include <stdint.h>
namespace nntpchan
{
/** @brief a buffered line reader */
class LineReader
{
public:
LineReader(size_t lineLimit);
/** @brief queue inbound data from connection */
void Data(const char * data, ssize_t s);
/** implements IConnHandler */
virtual bool ShouldClose();
protected:
/** @brief handle a line from the client */
virtual void HandleLine(const std::string & line) = 0;
private:
void OnLine(const char * d, const size_t l);
std::string m_leftovers;
bool m_close;
const size_t lineLimit;
};
}
#endif

View File

@@ -1,4 +1,4 @@
#include "mime.hpp"
#include <nntpchan/mime.hpp>
namespace nntpchan
{

View File

@@ -1,30 +0,0 @@
#ifndef NNTPCHAN_MIME_HPP
#define NNTPCHAN_MIME_HPP
#include "file_handle.hpp"
#include "io_handle.hpp"
#include <functional>
#include <map>
#include <string>
namespace nntpchan
{
typedef std::map<std::string, std::string> RawHeader;
bool ReadHeader(const FileHandle_ptr & f, RawHeader & h);
struct MimePart
{
virtual RawHeader & Header() = 0;
virtual IOHandle_ptr OpenPart() = 0;
};
typedef std::unique_ptr<MimePart> MimePart_ptr;
typedef std::function<bool(MimePart_ptr)> PartReader;
bool ReadParts(const FileHandle_ptr & f, PartReader r);
}
#endif

View File

@@ -1,59 +0,0 @@
#ifndef NNTPCHAN_MODEL_HPP
#define NNTPCHAN_MODEL_HPP
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <vector>
namespace nntpchan
{
namespace model
{
// MIME Header
typedef std::map<std::string, std::set<std::string> > PostHeader;
// text post contents
typedef std::string PostBody;
// single file attachment, (orig_filename, hexdigest, thumb_filename)
typedef std::tuple<std::string, std::string, std::string> PostAttachment;
// all attachments on a post
typedef std::vector<PostAttachment> Attachments;
// a post (header, Post Text, Attachments)
typedef std::tuple<PostHeader, PostBody, Attachments> Post;
// a thread (many posts in post order)
typedef std::vector<Post> Thread;
static inline std::string & GetFilename(PostAttachment & att)
{
return std::get<0>(att);
}
static inline std::string & GetHexDigest(PostAttachment & att)
{
return std::get<1>(att);
}
static inline std::string & GetThumbnail(PostAttachment & att)
{
return std::get<2>(att);
}
static inline PostHeader & GetHeader(Post & post)
{
return std::get<0>(post);
}
static inline PostBody & GetBody(Post & post)
{
return std::get<1>(post);
}
static inline Attachments & GetAttachments(Post & post)
{
return std::get<2>(post);
}
}
}
#endif

View File

@@ -1,4 +1,4 @@
#include "net.hpp"
#include <nntpchan/net.hpp>
#include <uv.h>
#include <sstream>
#include <stdexcept>

View File

@@ -1,23 +0,0 @@
#ifndef NNTPCHAN_NET_HPP
#define NNTPCHAN_NET_HPP
#include <sys/types.h>
#include <netinet/in.h>
#include <string>
namespace nntpchan
{
struct NetAddr
{
NetAddr();
sockaddr_in6 addr;
operator sockaddr * () { return (sockaddr *) &addr; }
operator const sockaddr * () const { return (sockaddr *) &addr; }
std::string to_string();
};
NetAddr ParseAddr(const std::string & addr);
}
#endif

View File

@@ -1,6 +1,6 @@
#include "nntp_auth.hpp"
#include "crypto.hpp"
#include "base64.hpp"
#include <nntpchan/nntp_auth.hpp>
#include <nntpchan/crypto.hpp>
#include <nntpchan/base64.hpp>
#include <array>
#include <iostream>
#include <fstream>

View File

@@ -1,61 +0,0 @@
#ifndef NNTPCHAN_NNTP_AUTH_HPP
#define NNTPCHAN_NNTP_AUTH_HPP
#include <string>
#include <iostream>
#include <fstream>
#include <mutex>
#include <memory>
#include "line.hpp"
namespace nntpchan
{
/** @brief nntp credential db interface */
class NNTPCredentialDB
{
public:
/** @brief open connection to database, return false on error otherwise return true */
virtual bool Open() = 0;
/** @brief close connection to database */
virtual void Close() = 0;
/** @brief return true if username password combo is correct */
virtual bool CheckLogin(const std::string & user, const std::string & passwd) = 0;
virtual ~NNTPCredentialDB() {}
};
typedef std::shared_ptr<NNTPCredentialDB> CredDB_ptr;
/** @brief nntp credential db using hashed+salted passwords */
class HashedCredDB : public NNTPCredentialDB, public LineReader
{
public:
HashedCredDB();
bool CheckLogin(const std::string & user, const std::string & passwd);
protected:
void SetStream(std::istream * i);
std::string Hash(const std::string & data, const std::string & salt);
void HandleLine(const std::string & line);
private:
bool ProcessLine(const std::string & line);
std::mutex m_access;
std::string m_user, m_passwd;
bool m_found;
/** return true if we have a line that matches this username / password combo */
std::istream * m_instream;
};
class HashedFileDB : public HashedCredDB
{
public:
HashedFileDB(const std::string & fname);
~HashedFileDB();
bool Open();
void Close();
private:
std::string m_fname;
std::ifstream f;
};
}
#endif

View File

@@ -1,5 +1,5 @@
#include "nntp_handler.hpp"
#include "sanitize.hpp"
#include <nntpchan/nntp_handler.hpp>
#include <nntpchan/sanitize.hpp>
#include <algorithm>
#include <cctype>
#include <cstring>

View File

@@ -1,62 +0,0 @@
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
#define NNTPCHAN_NNTP_HANDLER_HPP
#include <deque>
#include <string>
#include "line.hpp"
#include "nntp_auth.hpp"
#include "storage.hpp"
namespace nntpchan
{
class NNTPServerHandler : public LineReader, public IConnHandler
{
public:
NNTPServerHandler(const std::string & storage);
~NNTPServerHandler();
virtual bool ShouldClose();
void SetAuth(CredDB_ptr creds);
virtual void OnData(const char *, ssize_t);
void Greet();
protected:
void HandleLine(const std::string & line);
void HandleCommand(const std::deque<std::string> & command);
private:
enum State {
eStateReadCommand,
eStateStoreArticle,
eStateQuit
};
private:
void EnterState(State st);
void ArticleObtained();
// handle quit command, this queues a reply
void Quit();
// switch nntp modes, this queues a reply
void SwitchMode(const std::string & mode);
bool PostingAllowed();
private:
std::string m_articleName;
FileHandle_ptr m_article;
CredDB_ptr m_auth;
ArticleStorage m_store;
std::string m_mode;
bool m_authed;
State m_state;
};
}
#endif

View File

@@ -1,8 +1,8 @@
#include "nntp_server.hpp"
#include "nntp_auth.hpp"
#include "nntp_handler.hpp"
#include "net.hpp"
#include <nntpchan/nntp_server.hpp>
#include <nntpchan/nntp_auth.hpp>
#include <nntpchan/nntp_handler.hpp>
#include <nntpchan/net.hpp>
#include <cassert>
#include <iostream>
#include <sstream>

View File

@@ -1,62 +0,0 @@
#ifndef NNTPCHAN_NNTP_SERVER_HPP
#define NNTPCHAN_NNTP_SERVER_HPP
#include <uv.h>
#include <string>
#include <deque>
#include "frontend.hpp"
#include "server.hpp"
namespace nntpchan
{
class NNTPServer : public Server
{
public:
NNTPServer(uv_loop_t * loop);
virtual ~NNTPServer();
void SetStoragePath(const std::string & path);
void SetLoginDB(const std::string path);
void SetInstanceName(const std::string & name);
std::string InstanceName() const;
void Close();
virtual IServerConn * CreateConn(uv_stream_t * s);
virtual void OnAcceptError(int status);
void SetFrontend(Frontend * f);
private:
std::string m_logindbpath;
std::string m_storagePath;
std::string m_servername;
Frontend_ptr m_frontend;
};
class NNTPServerConn : public IServerConn
{
public:
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, Server * parent, IConnHandler * h) : IServerConn(l, s, parent, h) {}
virtual bool IsTimedOut() { return false; };
/** @brief send next queued reply */
virtual void SendNextReply();
virtual void Greet();
};
}
#endif

View File

@@ -1,11 +1,19 @@
#include "sanitize.hpp"
#include <nntpchan/sanitize.hpp>
#include <algorithm>
#include <regex>
#include <cctype>
namespace nntpchan
{
std::string NNTPSanitize(const std::string & str)
std::string NNTPSanitizeLine(const std::string & str)
{
if(str == ".") return " .";
std::string sane;
sane += str;
const char ch = ' ';
std::replace_if(sane.begin(), sane.end(), [](unsigned char ch) -> bool { return iscntrl(ch); } , ch);
return sane;
}
std::string ToLower(const std::string & str)
@@ -21,4 +29,11 @@ namespace nntpchan
{
return std::regex_search(msgid, re_ValidMessageID) == 1;
}
static const std::regex re_ValidNewsgroup("^[a-zA-Z][a-zA-Z0-9.]{1,128}$");
bool IsValidNewsgroup(const std::string & msgid)
{
return std::regex_search(msgid, re_ValidNewsgroup) == 1;
}
}

View File

@@ -1,12 +0,0 @@
#ifndef NNTPCHAN_SANITIZE_HPP
#define NNTPCHAN_SANITIZE_HPP
#include <string>
namespace nntpchan
{
std::string NNTPSanitize(const std::string & str);
std::string ToLower(const std::string & str);
bool IsValidMessageID(const std::string & msgid);
}
#endif

View File

@@ -1,6 +1,6 @@
#include "buffer.hpp"
#include "server.hpp"
#include "net.hpp"
#include <nntpchan/buffer.hpp>
#include <nntpchan/server.hpp>
#include <nntpchan/net.hpp>
#include <cassert>
#include <iostream>

View File

@@ -1,98 +0,0 @@
#ifndef NNTPCHAN_SERVER_HPP
#define NNTPCHAN_SERVER_HPP
#include <uv.h>
#include <deque>
#include <functional>
#include <string>
namespace nntpchan
{
class Server;
struct IConnHandler
{
virtual ~IConnHandler() {};
/** got inbound data */
virtual void OnData(const char * data, ssize_t s) = 0;
/** get next line of data to send */
std::string GetNextLine();
/** return true if we have a line to send */
bool HasNextLine();
/** return true if we should close this connection otherwise return false */
virtual bool ShouldClose() = 0;
/** queue a data send */
void QueueLine(const std::string & line);
virtual void Greet() = 0;
private:
std::deque<std::string> m_sendlines;
};
/** server connection handler interface */
struct IServerConn
{
IServerConn(uv_loop_t * l, uv_stream_t * s, Server * parent, IConnHandler * h);
virtual ~IServerConn();
virtual void Close();
virtual void Greet() = 0;
virtual void SendNextReply() = 0;
virtual bool IsTimedOut() = 0;
void SendString(const std::string & str);
Server * Parent() { return m_parent; };
IConnHandler * GetHandler() { return m_handler; };
uv_loop_t * GetLoop() { return m_loop; };
private:
uv_tcp_t m_conn;
uv_loop_t * m_loop;
Server * m_parent;
IConnHandler * m_handler;
};
class Server
{
public:
Server(uv_loop_t * loop);
/** called after socket close, NEVER call directly */
virtual ~Server() {}
/** create connection handler from open stream */
virtual IServerConn * CreateConn(uv_stream_t * s) = 0;
/** close all sockets and stop */
void Close();
/** bind to address */
void Bind(const std::string & addr);
typedef std::function<void(IServerConn *)> ConnVisitor;
/** visit all open connections */
void VisitConns(ConnVisitor v);
/** remove connection from server, called after proper close */
void RemoveConn(IServerConn * conn);
protected:
uv_loop_t * GetLoop() { return m_loop; }
virtual void OnAcceptError(int status) = 0;
private:
operator uv_handle_t * () { return (uv_handle_t*) &m_server; }
operator uv_tcp_t * () { return &m_server; }
operator uv_stream_t * () { return (uv_stream_t *) &m_server; }
void OnAccept(uv_stream_t * s, int status);
std::deque<IServerConn *> m_conns;
uv_tcp_t m_server;
uv_loop_t * m_loop;
};
}
#endif

View File

@@ -1,11 +0,0 @@
#ifndef NNTPCHAN_SHA1_HPP
#define NNTPCHAN_SHA1_HPP
#include <string>
namespace nntpchan
{
std::string sha1_hex(const std::string & data);
}
#endif

View File

@@ -1,8 +1,8 @@
#include "staticfile_frontend.hpp"
#include "file_handle.hpp"
#include "sanitize.hpp"
#include "mime.hpp"
#include "sha1.hpp"
#include <nntpchan/staticfile_frontend.hpp>
#include <nntpchan/file_handle.hpp>
#include <nntpchan/sanitize.hpp>
#include <nntpchan/mime.hpp>
#include <nntpchan/sha1.hpp>
#include <any>
#include <iostream>
#include <set>
@@ -36,70 +36,89 @@ namespace nntpchan
// read body
// render templates
std::map<std::string, std::any> thread_args;
auto findMsgidFunc = [](const std::pair<std::string, std::string> & item) -> bool {
auto lower = ToLower(item.first);
return (lower == "message-id") || (lower == "messageid");
};
auto msgid = std::find_if(header.begin(), header.end(), findMsgidFunc);
if(!IsValidMessageID(msgid->second))
{
std::clog << "invalid message-id: " << msgid->second << std::endl;
return;
}
std::string msgid_hash = sha1_hex(msgid->second);
fs::path threadFilePath = m_OutDir / fs::path("thread-" + msgid_hash + ".html");
if(m_TemplateEngine)
{
std::map<std::string, std::any> thread_args;
auto findMsgidFunc = [](const std::pair<std::string, std::string> & item) -> bool {
auto lower = ToLower(item.first);
return (lower == "message-id") || (lower == "messageid");
};
auto msgid = std::find_if(header.begin(), header.end(), findMsgidFunc);
std::string msgid_hash = sha1_hex(msgid->second);
fs::path threadFilePath = m_OutDir / fs::path("thread-" + msgid_hash + ".html");
FileHandle_ptr out = OpenFile(threadFilePath, eWrite);
if(!m_TemplateEngine->WriteTemplate("thread.mustache", thread_args, out))
{
std::clog << "failed to write " << threadFilePath << std::endl;
return;
}
}
std::set<std::string> newsgroups_list;
auto findNewsgroupsFunc = [](const std::pair<std::string, std::string> & item) -> bool
{
return ToLower(item.first) == "newsgroups";
};
std::set<std::string> newsgroups_list;
auto findNewsgroupsFunc = [](const std::pair<std::string, std::string> & item) -> bool
auto group = std::find_if(header.begin(), header.end(), findNewsgroupsFunc);
if(group == std::end(header))
{
std::clog << "no newsgroups header" << std::endl;
return;
}
std::istringstream input(group->second);
std::string newsgroup;
while(std::getline(input, newsgroup, ' '))
{
if(IsValidNewsgroup(newsgroup))
newsgroups_list.insert(newsgroup);
}
for(const auto & name : newsgroups_list)
{
auto board = GetThreadsPaginated(name, 10, m_Pages);
uint32_t pageno = 0;
for(Threads_t threads : board)
{
return ToLower(item.first) == "newsgroups";
};
auto group = std::find_if(header.begin(), header.end(), findNewsgroupsFunc);
if(group == std::end(header))
{
std::clog << "no newsgroups header" << std::endl;
return;
}
std::istringstream input(group->second);
std::string newsgroup;
while(std::getline(input, newsgroup, ' '))
{
newsgroups_list.insert(NNTPSanitize(newsgroup));
}
for(const auto & name : newsgroups_list)
{
auto board = GetThreadsPaginated(name, 10, m_Pages);
uint32_t pageno = 0;
for(Threads_t threads : board)
std::map<std::string, std::any> board_args;
board_args["group"] = std::make_any<std::string>(name);
board_args["pageno"] = std::make_any<uint32_t>(pageno);
board_args["threads"] = std::make_any<Threads_t>(threads);
fs::path boardPageFilename(newsgroup + "-" + std::to_string(pageno) + ".html");
if(m_TemplateEngine)
{
std::map<std::string, std::any> board_args;
board_args["group"] = std::make_any<std::string>(name);
board_args["pageno"] = std::make_any<uint32_t>(pageno);
board_args["threads"] = std::make_any<Threads_t>(threads);
fs::path boardPageFilename(newsgroup + "-" + std::to_string(pageno) + ".html");
out = OpenFile(m_OutDir / boardPageFilename, eWrite);
FileHandle_ptr out = OpenFile(m_OutDir / boardPageFilename, eWrite);
m_TemplateEngine->WriteTemplate("board.mustache", board_args, out);
++pageno;
}
++pageno;
}
}
}
}
bool StaticFileFrontend::AcceptsNewsgroup(const std::string & newsgroup)
{
return IsValidNewsgroup(newsgroup);
}
bool StaticFileFrontend::AcceptsMessage(const std::string & msgid)
{
return IsValidMessageID(msgid);
}
StaticFileFrontend::BoardPage_t StaticFileFrontend::GetThreadsPaginated(const std::string & group, uint32_t perpage, uint32_t pages)
{

View File

@@ -1,44 +0,0 @@
#ifndef NNTPCHAN_STATICFILE_FRONTEND_HPP
#define NNTPCHAN_STATICFILE_FRONTEND_HPP
#include "frontend.hpp"
#include "template_engine.hpp"
#include "model.hpp"
#include <experimental/filesystem>
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class StaticFileFrontend : public Frontend
{
public:
StaticFileFrontend(TemplateEngine * tmpl, const std::string & templateDir, const std::string & outDir, uint32_t pages);
~StaticFileFrontend();
void ProcessNewMessage(const fs::path & fpath);
bool AcceptsNewsgroup(const std::string & newsgroup) { (void) newsgroup; return true; }
bool AcceptsMessage(const std::string & msgid) { (void) msgid; return true; }
private:
typedef nntpchan::model::Thread Thread_t;
typedef std::vector<Thread_t> Threads_t;
typedef std::vector<Threads_t> BoardPage_t;
BoardPage_t GetThreadsPaginated(const std::string & group, uint32_t perpage, uint32_t pages);
private:
TemplateEngine_ptr m_TemplateEngine;
fs::path m_TemplateDir;
fs::path m_OutDir;
uint32_t m_Pages;
};
}
#endif

View File

@@ -1,5 +1,5 @@
#include "storage.hpp"
#include "sanitize.hpp"
#include <nntpchan/storage.hpp>
#include <nntpchan/sanitize.hpp>
#include <sstream>
namespace nntpchan

View File

@@ -1,40 +0,0 @@
#ifndef NNTPCHAN_STORAGE_HPP
#define NNTPCHAN_STORAGE_HPP
#include <experimental/filesystem>
#include <string>
#include "file_handle.hpp"
namespace nntpchan
{
namespace fs = std::experimental::filesystem;
class ArticleStorage
{
public:
ArticleStorage();
ArticleStorage(const fs::path & fpath);
~ArticleStorage();
void SetPath(const fs::path & fpath);
FileHandle_ptr OpenWrite(const std::string & msgid);
FileHandle_ptr OpenRead(const std::string & msgid);
/**
return true if we should accept a new message give its message id
*/
bool Accept(const std::string & msgid);
private:
fs::path MessagePath(const std::string & msgid);
fs::path basedir;
};
}
#endif

View File

@@ -1,5 +1,6 @@
#include "template_engine.hpp"
#include "sanitize.hpp"
#include <nntpchan/template_engine.hpp>
#include <nntpchan/sanitize.hpp>
#include <iostream>
namespace nntpchan
{
@@ -8,19 +9,33 @@ namespace nntpchan
{
struct Impl
{
bool RenderFile(const std::string & fname, const Args_t & args, const FileHandle_ptr & out)
bool ParseTemplate(const FileHandle_ptr & in)
{
auto file = OpenFile(fname, eRead);
return true;
}
bool RenderFile(const Args_t & args, const FileHandle_ptr & out)
{
return true;
}
};
virtual bool WriteTemplate(const std::string & fname, const Args_t & args, const FileHandle_ptr & out)
virtual bool WriteTemplate(const fs::path & fpath, const Args_t & args, const FileHandle_ptr & out)
{
auto templFile = OpenFile(fpath, eRead);
if(!templFile)
{
std::clog << "no such template at " << fpath << std::endl;
return false;
}
auto impl = std::make_unique<Impl>();
return impl->RenderFile(fname, args, out);
if(impl->ParseTemplate(templFile))
return impl->RenderFile(args, out);
std::clog << "failed to parse template " << fpath << std::endl;
return false;
}
};

View File

@@ -1,23 +0,0 @@
#ifndef NNTPCHAN_TEMPLATE_ENGINE_HPP
#define NNTPCHAN_TEMPLATE_ENGINE_HPP
#include "file_handle.hpp"
#include <any>
#include <map>
#include <memory>
#include <string>
namespace nntpchan
{
struct TemplateEngine
{
using Args_t = std::map<std::string, std::any>;
virtual bool WriteTemplate(const std::string & template_fname, const Args_t & args, const FileHandle_ptr & out) = 0;
};
TemplateEngine * CreateTemplateEngine(const std::string & dialect);
typedef std::unique_ptr<TemplateEngine> TemplateEngine_ptr;
}
#endif