track primordial goo frontends
This commit is contained in:
parent
9e9a1efe06
commit
cc089b3401
2
contrib/frontends/cpp/nntpchan-daemon/.gitignore
vendored
Normal file
2
contrib/frontends/cpp/nntpchan-daemon/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.o
|
||||||
|
nntpchan
|
29
contrib/frontends/cpp/nntpchan-daemon/Makefile
Normal file
29
contrib/frontends/cpp/nntpchan-daemon/Makefile
Normal 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)
|
6
contrib/frontends/cpp/nntpchan-daemon/nntpchan.ini
Normal file
6
contrib/frontends/cpp/nntpchan-daemon/nntpchan.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[nntp]
|
||||||
|
bind = [::]:1199
|
||||||
|
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
path = ./storage/
|
21
contrib/frontends/cpp/nntpchan-daemon/src/buffer.cpp
Normal file
21
contrib/frontends/cpp/nntpchan-daemon/src/buffer.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
contrib/frontends/cpp/nntpchan-daemon/src/buffer.hpp
Normal file
19
contrib/frontends/cpp/nntpchan-daemon/src/buffer.hpp
Normal 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
|
27
contrib/frontends/cpp/nntpchan-daemon/src/event.cpp
Normal file
27
contrib/frontends/cpp/nntpchan-daemon/src/event.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
contrib/frontends/cpp/nntpchan-daemon/src/event.hpp
Normal file
26
contrib/frontends/cpp/nntpchan-daemon/src/event.hpp
Normal 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
|
186
contrib/frontends/cpp/nntpchan-daemon/src/ini.hpp
Normal file
186
contrib/frontends/cpp/nntpchan-daemon/src/ini.hpp
Normal 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
|
||||||
|
|
78
contrib/frontends/cpp/nntpchan-daemon/src/main.cpp
Normal file
78
contrib/frontends/cpp/nntpchan-daemon/src/main.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
29
contrib/frontends/cpp/nntpchan-daemon/src/message.cpp
Normal file
29
contrib/frontends/cpp/nntpchan-daemon/src/message.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
36
contrib/frontends/cpp/nntpchan-daemon/src/message.hpp
Normal file
36
contrib/frontends/cpp/nntpchan-daemon/src/message.hpp
Normal 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
|
44
contrib/frontends/cpp/nntpchan-daemon/src/net.cpp
Normal file
44
contrib/frontends/cpp/nntpchan-daemon/src/net.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
23
contrib/frontends/cpp/nntpchan-daemon/src/net.hpp
Normal file
23
contrib/frontends/cpp/nntpchan-daemon/src/net.hpp
Normal 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
|
132
contrib/frontends/cpp/nntpchan-daemon/src/nntp_server.cpp
Normal file
132
contrib/frontends/cpp/nntpchan-daemon/src/nntp_server.cpp
Normal 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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
103
contrib/frontends/cpp/nntpchan-daemon/src/nntp_server.hpp
Normal file
103
contrib/frontends/cpp/nntpchan-daemon/src/nntp_server.hpp
Normal 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
|
42
contrib/frontends/cpp/nntpchan-daemon/src/storage.cpp
Normal file
42
contrib/frontends/cpp/nntpchan-daemon/src/storage.cpp
Normal 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 '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
35
contrib/frontends/cpp/nntpchan-daemon/src/storage.hpp
Normal file
35
contrib/frontends/cpp/nntpchan-daemon/src/storage.hpp
Normal 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
|
11
contrib/frontends/py/README.md
Normal file
11
contrib/frontends/py/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# python nntpchan demo frontend #
|
||||||
|
|
||||||
|
## usage ##
|
||||||
|
|
||||||
|
add to nntpchan.json hooks section:
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "pyfront",
|
||||||
|
"exec": "/path/to/frontend.py"
|
||||||
|
}
|
||||||
|
|
9
contrib/frontends/py/frontend.py
Normal file
9
contrib/frontends/py/frontend.py
Normal 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)
|
42
contrib/frontends/py/nntpchan/__init__.py
Normal file
42
contrib/frontends/py/nntpchan/__init__.py
Normal 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
|
||||||
|
|
Reference in New Issue
Block a user