Archived
1
0

track primordial goo frontends

This commit is contained in:
Jeff Becker 2016-10-15 09:12:01 -04:00
parent 9e9a1efe06
commit cc089b3401
No known key found for this signature in database
GPG Key ID: AB950234D6EA286B
20 changed files with 900 additions and 0 deletions

View File

@ -0,0 +1,2 @@
*.o
nntpchan

View File

@ -0,0 +1,29 @@
EXE = nntpchan
CXX = clang++
SRC_PATH = ./src
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
OBJECTS := $(SOURCES:.cpp=.o)
PKGS := libuv
LD_FLAGS := $(shell pkg-config --libs $(PKGS))
INC_FLAGS := -I $(SRC_PATH) $(shell pkg-config --cflags $(PKGS))
CXXFLAGS := -std=c++14 -Wall -Wextra $(INC_FLAGS)
all: $(EXE)
$(EXE): $(OBJECTS)
$(CXX) -o $(EXE) $(LD_FLAGS) $(OBJECTS)
%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@
clean:
rm -f $(OBJECTS) $(EXE)

View File

@ -0,0 +1,6 @@
[nntp]
bind = [::]:1199
[storage]
path = ./storage/

View File

@ -0,0 +1,21 @@
#include "buffer.hpp"
#include <cstring>
namespace nntpchan
{
WriteBuffer::WriteBuffer(const char * b, const size_t s)
{
char * buf = new char[s];
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()) {}
WriteBuffer::~WriteBuffer()
{
delete [] b.base;
}
}

View File

@ -0,0 +1,19 @@
#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

@ -0,0 +1,27 @@
#include "event.hpp"
#include <cassert>
namespace nntpchan
{
Mainloop::Mainloop()
{
m_loop = uv_default_loop();
assert(uv_loop_init(m_loop) == 0);
}
Mainloop::~Mainloop()
{
uv_loop_close(m_loop);
}
void Mainloop::Stop()
{
uv_stop(m_loop);
}
void Mainloop::Run(uv_run_mode mode)
{
uv_run(m_loop, mode);
}
}

View File

@ -0,0 +1,26 @@
#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

@ -0,0 +1,186 @@
/**
* 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

@ -0,0 +1,78 @@
#include "ini.hpp"
#include "storage.hpp"
#include "nntp_server.hpp"
#include "event.hpp"
#include <vector>
#include <string>
int main(int argc, char * argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
return 1;
}
nntpchan::Mainloop loop;
nntpchan::NNTPServer nntp(loop);
std::string fname(argv[1]);
std::ifstream i(fname);
if(i.is_open()) {
INI::Parser conf(i);
std::vector<std::string> requiredSections = {"nntp", "storage"};
auto & level = conf.top();
for ( const auto & section : requiredSections ) {
if(level.sections.find(section) == level.sections.end()) {
std::cerr << "config file " << fname << " does not have required section: ";
std::cerr << section << std::endl;
return 1;
}
}
auto & storeconf = level.sections["storage"].values;
if (storeconf.find("path") == storeconf.end()) {
std::cerr << "storage section does not have 'path' value" << std::endl;
return 1;
}
nntp.SetStoragePath(storeconf["path"]);
auto & nntpconf = level.sections["nntp"].values;
if (nntpconf.find("bind") == nntpconf.end()) {
std::cerr << "nntp section does not have 'bind' value" << std::endl;
return 1;
}
auto & a = nntpconf["bind"];
try {
nntp.Bind(a);
} catch ( std::exception & ex ) {
std::cerr << "failed to bind: " << ex.what() << std::endl;
return 1;
}
try {
std::cerr << "run mainloop" << std::endl;
loop.Run();
} catch ( std::exception & ex ) {
std::cerr << "exception in mainloop: " << ex.what() << std::endl;
return 2;
}
} else {
std::cerr << "failed to open " << fname << std::endl;
return 1;
}
}

View File

@ -0,0 +1,29 @@
#include "message.hpp"
namespace nntpchan
{
bool IsValidMessageID(const MessageID & msgid)
{
auto itr = msgid.begin();
auto end = msgid.end();
--end;
if (*itr != '<') return false;
if (*end != '>') return false;
bool atfound = false;
while(itr != end) {
auto c = *itr;
++itr;
if(atfound && c == '@') return false;
if(c == '@') {
atfound = true;
continue;
}
if (c == '$' || c == '_' || c == '-') continue;
if (c > '0' && c < '9') continue;
if (c > 'A' && c < 'Z') continue;
if (c > 'a' && c < 'z') continue;
return false;
}
return true;
}
}

View File

@ -0,0 +1,36 @@
#ifndef NNTPCHAN_MESSAGE_HPP
#define NNTPCHAN_MESSAGE_HPP
#include <string>
#include <vector>
#include <map>
#include <functional>
namespace nntpchan
{
typedef std::string MessageID;
bool IsValidMessageID(const MessageID & msgid);
typedef std::pair<std::string, std::string> MessageHeader;
typedef std::map<std::string, std::string> MIMEPartHeader;
typedef std::function<bool(const MessageHeader &)> MessageHeaderFilter;
typedef std::function<bool(const MIMEPartHeader &)> MIMEPartFilter;
/**
read MIME message from i,
filter each header with h,
filter each part with p,
store result in o
return true if we read the whole message, return false if there is remaining
*/
bool StoreMIMEMessage(std::istream & i, MessageHeaderFilter h, MIMEPartHeader p, std::ostream & o);
}
#endif

View File

@ -0,0 +1,44 @@
#include "net.hpp"
#include <uv.h>
#include <sstream>
#include <stdexcept>
#include <cstring>
namespace nntpchan
{
std::string NetAddr::to_string()
{
std::string str("invalid");
const size_t s = 128;
char * buff = new char[s];
if(uv_ip6_name(&addr, buff, s) == 0) {
str = std::string(buff);
delete [] buff;
}
std::stringstream ss;
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
return ss.str();
}
NetAddr::NetAddr()
{
std::memset(&addr, 0, sizeof(addr));
}
NetAddr ParseAddr(const std::string & addr)
{
NetAddr saddr;
auto n = addr.rfind("]:");
if (n == std::string::npos) {
throw std::runtime_error("invalid address: "+addr);
}
if (addr[0] != '[') {
throw std::runtime_error("invalid address: "+addr);
}
auto p = addr.substr(n+2);
int port = std::atoi(p.c_str());
auto a = addr.substr(0, n);
uv_ip6_addr(a.c_str(), port, &saddr.addr);
return saddr;
}
}

View File

@ -0,0 +1,23 @@
#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

@ -0,0 +1,132 @@
#include "buffer.hpp"
#include "nntp_server.hpp"
#include "net.hpp"
#include <cassert>
#include <iostream>
#include <sstream>
namespace nntpchan
{
NNTPServer::NNTPServer(uv_loop_t * loop)
{
uv_tcp_init(loop, &m_server);
m_loop = loop;
}
NNTPServer::~NNTPServer()
{
uv_close((uv_handle_t*)&m_server, [](uv_handle_t *) {});
}
void NNTPServer::Bind(const std::string & addr)
{
auto saddr = ParseAddr(addr);
assert(uv_tcp_bind(*this, saddr, 0) == 0);
std::cerr << "nntp server bound to " << saddr.to_string() << std::endl;
m_server.data = this;
auto cb = [] (uv_stream_t * s, int status) {
NNTPServer * self = (NNTPServer *) s->data;
self->OnAccept(s, status);
};
assert(uv_listen(*this, 5, cb) == 0);
}
void NNTPServer::OnAccept(uv_stream_t * s, int status)
{
if(status < 0) {
std::cerr << "nntp server OnAccept fail: " << uv_strerror(status) << std::endl;
return;
}
NNTPServerConn * conn = new NNTPServerConn(m_loop, s, m_storagePath);
conn->SendCode(200, "Posting Allowed");
}
void NNTPServer::SetStoragePath(const std::string & path)
{
m_storagePath = path;
}
NNTPServerConn::NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage) :
m_handler(storage)
{
uv_tcp_init(l, &m_conn);
m_conn.data = this;
uv_accept(s, (uv_stream_t*) &m_conn);
uv_read_start((uv_stream_t*) &m_conn, [] (uv_handle_t * h, size_t s, uv_buf_t * b) {
NNTPServerConn * self = (NNTPServerConn*) h->data;
b->base = self->m_readbuff;
if (s > sizeof(self->m_readbuff))
b->len = sizeof(self->m_readbuff);
else
b->len = s;
}, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) {
NNTPServerConn * self = (NNTPServerConn*) s->data;
if(nread > 0) {
self->ProcessData(b->base, nread);
self->SendNextReply();
} else {
if (nread != UV_EOF) {
std::cerr << "error in nntp server conn alloc: ";
std::cerr << uv_strerror(nread);
std::cerr << std::endl;
}
delete self;
s->data = nullptr;
}
});
}
NNTPServerConn::~NNTPServerConn()
{
uv_close((uv_handle_t*)&m_conn, [] (uv_handle_t *) {});
}
void NNTPServerConn::ProcessData(const char *d, ssize_t l)
{
m_handler.OnData(d, l);
}
void NNTPServerConn::SendNextReply()
{
if(m_handler.HasNextLine()) {
auto line = m_handler.GetNextLine();
SendLine(line);
}
}
void NNTPServerConn::SendCode(const int code, const std::string & msg)
{
std::stringstream ss;
ss << code << " " << msg << std::endl;
SendString(ss.str());
}
void NNTPServerConn::SendString(const std::string & line)
{
WriteBuffer * b = new WriteBuffer(line);
uv_write(&b->w, *this, &b->b, 1, [](uv_write_t * w, int status) {
WriteBuffer * wb = (WriteBuffer *) w->data;
delete wb;
});
}
NNTPServerHandler::NNTPServerHandler(const std::string & storagepath) :
m_state(eNNTPStateGreet)
{
m_storage.SetPath(storagepath);
}
NNTPServerHandler::~NNTPServerHandler()
{
}
void NNTPServerHandler::OnData(const char * d, ssize_t l)
{
}
}

View File

@ -0,0 +1,103 @@
#ifndef NNTPCHAN_NNTP_SERVER_HPP
#define NNTPCHAN_NNTP_SERVER_HPP
#include <uv.h>
#include <string>
#include <vector>
#include "storage.hpp"
namespace nntpchan
{
class NNTPServerConn;
class NNTPServer
{
public:
NNTPServer(uv_loop_t * loop);
~NNTPServer();
void Bind(const std::string & addr);
void OnAccept(uv_stream_t * s, int status);
void SetStoragePath(const std::string & path);
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; }
uv_tcp_t m_server;
uv_loop_t * m_loop;
std::vector<NNTPServerConn *> m_conns;
std::string m_storagePath;
};
class NNTPServerHandler
{
public:
enum State {
eNNTPStateGreet,
eNNTPStateHandshake,
eNNTPStateReader,
eNNTPStateStream,
eNNTPStateTAKETHIS,
eNNTPStateIHAVE,
eNNTPStateARTICLE,
eNNTPStatePOST
};
NNTPServerHandler(const std::string & storagepath);
~NNTPServerHandler();
void OnData(const char * data, ssize_t s);
bool HasNextLine();
std::string GetNextLine();
private:
State m_state;
ArticleStorage m_storage;
};
class NNTPServerConn
{
public:
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, const std::string & storage);
virtual ~NNTPServerConn();
void Close();
void Quit();
void SendLine(const std::string & line);
void SendCode(const int code, const std::string & message);
void ProcessData(const char * d, ssize_t l);
void SendNextReply();
private:
void SendString(const std::string & line);
operator uv_stream_t * () { return (uv_stream_t *) &m_conn; }
uv_tcp_t m_conn;
NNTPServerHandler m_handler;
char m_readbuff[1024];
};
}
#endif

View File

@ -0,0 +1,42 @@
#include "storage.hpp"
#include <errno.h>
#include <sys/stat.h>
#include <sstream>
namespace nntpchan
{
ArticleStorage::ArticleStorage()
{
}
ArticleStorage::~ArticleStorage()
{
}
void ArticleStorage::SetPath(const std::string & fpath)
{
basedir = fpath;
// quiet fail
// TODO: check for errors
mkdir(basedir.c_str(), 0700);
}
bool ArticleStorage::Accept(const MessageID & msgid)
{
if (!IsValidMessageID(msgid)) return false;
std::stringstream ss;
ss << basedir << GetPathSep() << msgid;
auto s = ss.str();
FILE * f = fopen(s.c_str(), "r");
if ( f == nullptr) return errno == ENOENT;
fclose(f);
return false;
}
char ArticleStorage::GetPathSep()
{
return '/';
}
}

View File

@ -0,0 +1,35 @@
#ifndef NNTPCHAN_STORAGE_HPP
#define NNTPCHAN_STORAGE_HPP
#include <string>
#include "message.hpp"
namespace nntpchan
{
class ArticleStorage
{
public:
ArticleStorage();
~ArticleStorage();
void SetPath(const std::string & fpath);
std::ostream & OpenWrite(const MessageID & msgid);
std::istream & OpenRead(const MessageID & msgid);
/**
return true if we should accept a new message give its message id
*/
bool Accept(const MessageID & msgid);
private:
static char GetPathSep();
std::string basedir;
};
}
#endif

View File

@ -0,0 +1,11 @@
# python nntpchan demo frontend #
## usage ##
add to nntpchan.json hooks section:
{
"name": "pyfront",
"exec": "/path/to/frontend.py"
}

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python3
import nntpchan
import sys
if __name__ == "__main__":
msgid = sys.argv[1]
group = sys.argv[2]
if nntpchan.addArticle(msgid, group):
nntpchan.regenerate(msgid, group)

View File

@ -0,0 +1,42 @@
from nntpchan import store
from nntpchan import message
from nntpchan import preprocess
def addArticle(msgid, group):
"""
add article to system
:return True if we need to regenerate otherwise False:
"""
msg = None
# open message
with store.openArticle(msgid) as f:
# read article header
hdr = message.readHeader(f)
mclass = message.MultipartMessage
if hdr.isTextOnly:
# treat as text message instead of multipart
mclass = message.TextMessage
elif hdr.isSigned:
# treat as signed message
mclass = message.TripcodeMessage
# create messgae
msg = mclass(hdr, f)
if msg is not None:
# we got a message that is valid
store.storeMessage(msg)
else:
# invalid message
print("invalid message: {}".format(msgid))
return msg is not None
def regenerate(msgid, group):
"""
regenerate markup
"""
pass