Commit latest progress

develop
Thor 4 years ago
parent 4ca867ceab
commit a4abb2ac91
  1. 14
      include/log.hpp
  2. 2
      include/log/internal.hpp
  3. 41
      include/smtpd/filter_request.hpp
  4. 2
      include/smtpd/iosprotocol.hpp
  5. 4
      include/smtpd/mail.hpp
  6. 6
      include/smtpd/message.hpp
  7. 22
      include/smtpd/protocol.hpp
  8. 27
      include/smtpd/request.hpp
  9. 25
      include/smtpd/session.hpp
  10. 4
      include/smtpd/smtpd.hpp
  11. 32
      include/smtpd/token.hpp
  12. 7
      include/smtpd/types.hpp
  13. 132
      include/sparsevector.hpp
  14. 13
      include/util.hpp
  15. 53
      src/smtpd/filter_request.cpp
  16. 6
      src/smtpd/iosprotocol.cpp
  17. 4
      src/smtpd/message.cpp
  18. 97
      src/smtpd/protocol.cpp
  19. 36
      src/smtpd/request.cpp
  20. 26
      src/smtpd/session.cpp
  21. 128
      src/smtpd/token.cpp
  22. 15
      src/util.cpp

@ -14,7 +14,7 @@ namespace log {
void close();
template <typename... Ts>
void log(Level atLevel, const char * format, Ts... args) {
void log(Level atLevel, const char * format, const Ts & ... args) {
if(atLevel >= internal::level) {
std::cerr << "[" << internal::LEVEL_LABELS.at(atLevel) << "] ";
internal::printFormatted(util::lpad(format, internal::group).c_str(), args...);
@ -24,31 +24,31 @@ namespace log {
// Library debug
template <typename... Ts>
void ldebug(Ts... args) {
void ldebug(const Ts & ... args) {
log(LDEBUG, args...);
}
template <typename... Ts>
void debug(Ts... args) {
void debug(const Ts & ... args) {
log(DEBUG, args...);
}
template <typename... Ts>
void info(Ts... args) {
void info(const Ts & ... args) {
log(INFO, args...);
}
template <typename... Ts>
void warning(Ts... args) {
void warning(const Ts &... args) {
log(WARNING, args...);
}
template <typename... Ts>
void error(Ts... args) {
void error(const Ts & ... args) {
log(ERROR, args...);
}
template <typename... Ts>
void critical(Ts... args) {
void critical(const Ts & ... args) {
log(CRITICAL, args...);
}
}

@ -25,7 +25,7 @@ namespace log {
void printFormatted(const char * format);
template<typename T, typename... Ts>
void printFormatted(const char * format, T first, Ts... rest) {
void printFormatted(const char * format, const T & first, const Ts & ... rest) {
// Iterate characters in (rest of) format string
for (; *format != '\0'; ++format) {
// Check for placeholder character

@ -1,41 +0,0 @@
#ifndef _SMTPD_REQUEST_
#define _SMTPD_REQUEST_
#include <smtpd/session.hpp>
#include <smtpd/message.hpp>
#include <smtpd/mail.hpp>
#include <smtpd/types.hpp>
#include <memory>
#include <string>
#include <any>
namespace smtpd {
class FilterRequest {
public:
FilterRequest(Session & session, RequestToken requestToken);
// Responses
void sendFilterResult(TokenVector tokenVector);
void submitMail(const Mail & mail);
void proceed();
void junk();
void fail(String response = "451 Aborted");
void commit();
Session & session;
const RequestToken requestToken;
std::any data;
private:
TokenVector withAddress(TokenVector tokenVector);
void submitMailSection(const StringVector & lines, bool isBody);
void submitDataLine(const String & line, bool inBody);
};
}
#endif

@ -11,7 +11,7 @@ namespace smtpd {
void run();
protected:
virtual void emit(String output);
virtual void emit(std::string output);
private:
std::istream & in;

@ -9,8 +9,8 @@
namespace smtpd {
struct Mail {
bool hasHeader = false;
StringVector header;
StringVector body;
std::vector<std::string> header;
std::vector<std::string> body;
};
};

@ -6,17 +6,17 @@
namespace smtpd {
class Message {
public:
Message(String input, TokenPattern pattern);
Message(std::string input, TokenPattern pattern);
// Raw protocol string
const String input;
const std::string input;
// TokenPattern that matched this message
const TokenPattern pattern;
// Retrieve message metadata
SessionID sessionID() const;
RequestToken token() const;
RequestToken requestToken() const;
};
}

@ -1,6 +1,7 @@
#ifndef _SMTPD_PROTOCOL_
#define _SMTPD_PROTOCOL_
#include <smtpd/session.hpp>
#include <smtpd/message.hpp>
#include <smtpd/token.hpp>
#include <smtpd/types.hpp>
@ -9,29 +10,38 @@
#include <memory>
#include <functional>
#include <map>
#include <any>
namespace smtpd {
class Protocol;
using ProtocolListener = std::function<void(Protocol & pProtocol, const Message & message)>;
class Request;
using ProtocolListener = std::function<void(Protocol & protocol, const Message & message)>;
using SessionMap = std::unordered_map<SessionID, Session>;
class Protocol {
public:
String version;
std::string version;
Protocol();
void addListener(TokenPattern pattern, ProtocolListener listener);
void send(TokenVector tokens);
void addListener(const TokenPattern & pattern, ProtocolListener listener);
void send(const TokenPattern & pattern);
SessionMap sessions;
protected:
void absorb(String input);
virtual void emit(String output) = 0;
virtual void emit(std::string output) = 0;
void absorb(std::string input);
private:
std::multimap<TokenPattern, ProtocolListener> listeners;
static void configListener(Protocol & protocol, const Message & message);
static void sessionListener(Protocol & protocol, const Message & message);
static void requestListener(Protocol & protocol, const Message & message);
};
}
#endif

@ -0,0 +1,27 @@
#include <smtpd/mail.hpp>
#include <smtpd/types.hpp>
#include <smtpd/token.hpp>
#include <any>
#include <functional>
namespace smtpd {
class Request {
public:
void proceed();
void junk();
void fail(std::string response = "451 Aborted");
void commit();
void sendFilterResult(TokenPattern pattern);
void submitMail(const Mail & mail);
std::any payload;
std::function<void(Token command, TokenPattern arguments)> send;
private:
void submitMailSection(const std::vector<std::string> & lines, bool isBody);
void submitDataLine(std::string line, bool inBody);
};
}

@ -1,28 +1,15 @@
#ifndef _SMTPD_SESSION_
#define _SMTPD_SESSION_
#include <smtpd/protocol.hpp>
#include <smtpd/request.hpp>
#include <smtpd/types.hpp>
#include <unordered_map>
#include <functional>
namespace smtpd {
class Session;
using SessionMap = std::unordered_map<SessionID, Session>;
using ProtocolMap = std::unordered_map<size_t, SessionMap>;
using RequestMap = std::unordered_map<RequestToken, Request>;
class Session {
public:
Protocol & protocol;
const SessionID id;
RequestMap requests;
Session(Protocol & protocol, const SessionID & sessionID);
static void sessionListener(Protocol & protocol, const Message & message);
private:
static ProtocolMap protocolMap;
std::function<void(Token command, Token requestID, TokenPattern arguments)> send;
};
}
#endif
}

@ -4,9 +4,7 @@
/* In this file, and the library in general, headers are always in reverse order
* of dependence. This helps weed out missing includes to some degree. */
#include <smtpd/filter_request.hpp>
#include <smtpd/session.hpp>
#include <smtpd/ios_protocol.hpp>
#include <smtpd/iosprotocol.hpp>
#include <smtpd/protocol.hpp>
#include <smtpd/message.hpp>
#include <smtpd/mail.hpp>

@ -3,6 +3,8 @@
#include <smtpd/types.hpp>
#include <sparsevector.hpp>
#include <vector>
#include <map>
#include <string>
@ -10,29 +12,21 @@
#include <memory>
namespace smtpd {
using Token = String;
using TokenVector = std::vector<Token>;
TokenVector toTokenVector(String input, size_t maxTokens = -1);
String toString(TokenVector tokenVector);
class TokenPattern;
String toString(TokenPattern pattern);
class TokenPattern : public std::map<size_t, Token> {
private:
bool wildcard;
class TokenPattern : public sparse_vector<Token> {
public:
TokenPattern();
TokenPattern(TokenVector tokens, bool wildcard = false);
TokenPattern(String input, bool wildcard = true);
TokenVector toTokens() const;
TokenPattern(const TokenPattern & pattern);
TokenPattern(const std::vector<std::string> & input, bool wildcard = false);
TokenPattern(const std::string & input, bool wildcard = false);
TokenPattern(std::initializer_list<Token> list);
operator std::string() const;
friend std::ostream& operator<<(std::ostream & out, const TokenPattern& pattern);
friend bool operator<(const TokenPattern& l, const TokenPattern& r);
friend bool operator==(const TokenPattern& l, const TokenPattern& r);
private:
bool wildcard;
};
bool operator<(const TokenPattern & left, const TokenPattern & right);
std::ostream & operator<<(std::ostream & os, const TokenPattern & pattern);
}
#endif

@ -7,11 +7,10 @@
#include <functional>
namespace smtpd {
using String = std::string;
using StringVector = std::vector<String>;
using Token = std::string;
using SessionID = String;
using RequestToken = String;
using SessionID = std::string;
using RequestToken = std::string;
}

@ -0,0 +1,132 @@
#include <vector>
#include <map>
#include <iterator>
template <class T>
class sparse_vector {
std::map<size_t, T> map;
size_t _size = 0;
public:
const T empty;
template <class U>
class iterator : public std::iterator<std::input_iterator_tag, U> {
sparse_vector<U> & vector;
size_t index;
public:
iterator(sparse_vector<U> & vector, size_t index) : vector(vector), index(index) {}
iterator(const iterator<U> & from) : vector(from.vector), index(from.index) {}
// Prefix increment operator
iterator<U> & operator++() {
++index;
return *this;
}
// Postfix increment operator
iterator<U> operator++(int) {
iterator<U> tmp(*this);
operator++();
return tmp;
}
bool operator==(const iterator<U> & rhs) const {
return index == rhs.index;
}
bool operator!=(const iterator<U> & rhs) const {
return index != rhs.index;
}
U & operator*() const {
return vector.at(index);
}
};
sparse_vector() {}
// Construct from element list given as arguments
sparse_vector(std::initializer_list<T> list) {
size_t index = 0;
for(auto element : list) {
map[index] = element;
}
}
// Copy constructor
sparse_vector(const sparse_vector<T> & svector) {
map = svector.map;
_size = svector._size;
}
// Move constructor
sparse_vector(sparse_vector<T> && svector) {
map = std::move(svector.map);
_size = svector._size;
svector._size = 0;
}
// Construct from regular vector
sparse_vector(const std::vector<T> vector) {
for(size_t index = 0; index < vector.size(); ++index) {
map[index] = vector.at(index);
}
_size = vector.size();
}
// Move assignment operator
sparse_vector<T> & operator=(sparse_vector<T> && svector) {
map = std::move(svector.map);
size = svector._size;
svector._size = 0;
return *this;
}
size_t size() const {
return _size;
}
const T & at(size_t index) const {
return map.at(index);
}
bool has(size_t index) const {
return map.count(index) == 1;
}
void push_back(const T & element) {
map[_size++] = element;
}
template <class Range>
void push_back(Range range) {
for(T & element : range) {
push_back(element);
}
}
T pop_back() {
--_size;
if(has(_size)) {
T element = map->at(_size);
map.erase(_size);
return element;
}
return empty;
}
iterator<T> begin() const {
return iterator<T>(*this, 0);
}
iterator<T> end() const {
return iterator<T>(*this, size());
}
T & operator[](size_t index) {
return map[index];
}
};

@ -6,8 +6,19 @@
namespace util {
const std::vector<std::string> split(const std::string input, const std::string::value_type separator, const size_t max_tokens = -1);
const std::string join(const std::vector<std::string> strings, const std::string::value_type separator);
const std::string lpad(const std::string input, const int length = 1, const std::string str = " ");
template <class RangeT>
const std::string join(RangeT & range, const std::string::value_type separator) {
std::string output;
for(auto element = range.begin(); element != range.end(); ++element) {
if(element != range.begin()) {
output.push_back(separator);
}
output.append(*element);
}
return output;
}
};
#endif

@ -1,53 +0,0 @@
#include <smtpd/filter_request.hpp>
using namespace std;
namespace smtpd {
FilterRequest::FilterRequest(Session & session, RequestToken requestToken) :
session(session), requestToken(requestToken) { }
void FilterRequest::sendFilterResult(TokenVector tokenVector) {
tokenVector.insert(tokenVector.begin(), {"filter-result", "", ""});
session.protocol.send(withAddress(tokenVector));
}
void FilterRequest::proceed() {
sendFilterResult({"proceed"});
}
void FilterRequest::junk() {
sendFilterResult({"junk"});
}
void FilterRequest::fail(String response) {
sendFilterResult({"disconnect", response});
}
void FilterRequest::submitMail(const Mail & mail) {
submitMailSection(mail.header, false);
submitMailSection(mail.body, true);
}
void FilterRequest::submitMailSection(const StringVector & lines, bool isBody) {
for(auto line : lines) {
submitDataLine(line, isBody);
}
submitDataLine(isBody ? "." : "", false);
}
void FilterRequest::submitDataLine(const String & line, bool inBody) {
session.protocol.send(withAddress({"filter-dataline", "?", "?",
inBody && line == "." ? ".." : line}));
}
TokenVector FilterRequest::withAddress(TokenVector tokenVector) {
if(session.protocol.version < "0.5") {
tokenVector.at(1) = requestToken;
tokenVector.at(2) = session.id;
} else {
tokenVector.at(1) = session.id;
tokenVector.at(2) = requestToken;
}
return tokenVector;
}
}

@ -1,4 +1,4 @@
#include <smtpd/ios_protocol.hpp>
#include <smtpd/iosprotocol.hpp>
#include <log.hpp>
#include <iostream>
@ -9,13 +9,13 @@ namespace smtpd {
IOSProtocol::IOSProtocol(std::istream & in, std::ostream & out) :
in(in), out(out) { }
void IOSProtocol::emit(String output) {
void IOSProtocol::emit(string output) {
out << output << endl;
}
void IOSProtocol::run() {
while(!in.eof()) {
String line;
string line;
std::getline(in, line);
absorb(line);
}

@ -4,14 +4,14 @@
using namespace std;
namespace smtpd {
Message::Message(String input, TokenPattern pattern) :
Message::Message(string input, TokenPattern pattern) :
input(input), pattern(pattern) { }
SessionID Message::sessionID() const {
return pattern.at(5);
}
RequestToken Message::token() const {
RequestToken Message::requestToken() const {
return pattern.at(6);
}
}

@ -4,10 +4,9 @@
#include <util.hpp>
#include <log.hpp>
#include <smtpd/types.hpp>
#include <smtpd/message.hpp>
#include <smtpd/protocol.hpp>
#include <smtpd/session.hpp>
#include <smtpd/message.hpp>
#include <smtpd/types.hpp>
#include <iostream>
#include <set>
@ -18,56 +17,92 @@ using namespace util;
namespace smtpd {
Protocol::Protocol() {
addListener({"config|ready"}, Protocol::configListener);
addListener({"filter|*|*|smtp-in"}, Session::sessionListener);
addListener({"filter|"}, Protocol::sessionListener);
addListener({"filter|*|*|smtp-in"}, Protocol::requestListener);
}
void Protocol::addListener(const TokenPattern & pattern, ProtocolListener listener) {
log::ldebug("Adding passed-as-arguments listener for pattern %", pattern);
listeners.insert({pattern, listener});
listeners.insert(std::pair<TokenPattern, ProtocolListener>{pattern, listener});
}
void Protocol::send(const TokenPattern & pattern) {
log::ldebug("Sending pattern: %", pattern);
this->emit(pattern);
}
void Protocol::absorb(string input) {
TokenPattern pattern(input);
log::ldebug("Received pattern: %", pattern);
if(pattern.size() >= 2 && pattern.at(0) == "filter") {
version = pattern.at(1);
}
auto range = listeners.equal_range(pattern);
if(range.first == range.second) {
log::error("Unsupported message: %", pattern);
return;
}
for_each(range.first, range.second, [this, input](const auto & listenerPair) {
log::ldebug("Matched listener for: %", toString(listenerPair.first));
listenerPair.second(*this, Message{input, listenerPair.first});
});
}
void Protocol::configListener(Protocol & protocol, const Message & message) {
// Find unique SMTPD filters in listener map
set<TokenVector> uniqueFilters;
set<TokenPattern> uniqueFilters;
for(auto&& [pattern, listener] : protocol.listeners) {
log::ldebug("Found listener for: %", toString(pattern));
if(pattern.at(0) == "filter" && pattern.count(3) > 0 && pattern.count(4) > 0) {
log::ldebug("Found listener for: %", pattern);
if(pattern.at(0) == "filter" && pattern.has(3) && pattern.has(4) > 0) {
uniqueFilters.insert({"register", "filter", pattern.at(3), pattern.at(4)});
continue;
}
}
// Register filters with SMTPD
for(TokenVector message : uniqueFilters) {
for(TokenPattern message : uniqueFilters) {
protocol.send(message);
}
protocol.send({"register", "ready"});
}
void Protocol::addListener(TokenPattern pattern, ProtocolListener listener) {
log::ldebug("Adding passed-as-arguments listener for pattern %", toString(pattern));
listeners.insert({pattern, listener});
}
void Protocol::sessionListener(Protocol & protocol, const Message & message) {
auto sessionID = message.sessionID();
if(protocol.sessions.count(sessionID) == 0) {
auto & session = protocol.sessions[sessionID];
session.send = [&protocol, &sessionID](Token command, RequestToken requestToken, TokenPattern arguments) {
TokenPattern pattern = {command};
void Protocol::send(TokenVector tokens) {
log::ldebug("Sending tokens: %", toString(tokens));
this->emit(toString(tokens));
}
if(protocol.version < "0.5") {
pattern.push_back(requestToken);
pattern.push_back(sessionID);
} else {
pattern.push_back(sessionID);
pattern.push_back(requestToken);
}
void Protocol::absorb(String input) {
auto tokens = toTokenVector(input);
log::ldebug("Received tokens: %", toString(tokens));
for(auto & argument : arguments) {
pattern.push_back(argument);
}
if(tokens.size() >= 2 && tokens.at(0) == "filter") {
version = tokens.at(1);
protocol.send(pattern);
};
}
}
auto pattern = TokenPattern(tokens);
auto range = listeners.equal_range(pattern);
if(range.first == range.second) {
log::error("Unsupported message: %", toString(pattern));
return;
void Protocol::requestListener(Protocol & protocol, const Message & message) {
Session & session = protocol.sessions.at(message.sessionID());
auto requestToken = message.requestToken();
if(session.requests.count(requestToken) == 0) {
auto & request = session.requests[requestToken];
request.send = [&session, requestToken](Token command, TokenPattern arguments) {
session.send(command, requestToken, arguments);
};
}
for_each(range.first, range.second, [this, input](const auto & listenerPair) {
log::ldebug("Matched listener for: %", toString(listenerPair.first));
listenerPair.second(*this, Message{input, listenerPair.first});
});
}
}

@ -0,0 +1,36 @@
#include <smtpd/request.hpp>
#include <vector>
#include <string>
using namespace std;
namespace smtpd {
void Request::proceed() {
send("filter-result", {"proceed"});
}
void Request::junk() {
send("filter-result", {"junk"});
}
void Request::fail(string response) {
send("filter-result", {"disconnect", response});
}
void Request::submitMail(const Mail & mail) {
submitMailSection(mail.header, false);
submitMailSection(mail.body, true);
}
void Request::submitMailSection(const vector<string> & lines, bool isBody) {
for(auto line : lines) {
submitDataLine(line, isBody);
}
submitDataLine(isBody ? "." : "", false);
}
void Request::submitDataLine(string line, bool inBody) {
send("filter-dataline", {inBody && line == "." ? ".." : line});
}
}

@ -1,31 +1,5 @@
#include <smtpd/protocol.hpp>
#include <smtpd/session.hpp>
using namespace std;
namespace smtpd {
ProtocolMap Session::protocolMap{};
Session::Session(Protocol & protocol, const SessionID & sessionID) :
protocol(protocol), id(sessionID) { }
void Session::sessionListener(Protocol & protocol, const Message & message) {
size_t protocolID = hash<Protocol *>{}(&protocol);
SessionMap * pSessionMap;
try {
pSessionMap = &protocolMap.at(protocolID);
} catch(out_of_range & e) {
auto element = protocolMap.emplace(protocolID, SessionMap{});
pSessionMap = &element.first->second;
}
Session * pSession;
auto sessionID = message.sessionID();
try {
pSession = &pSessionMap->at(sessionID);
} catch(out_of_range & e) {
auto element = pSessionMap->emplace(sessionID, Session{protocol, sessionID});
pSession = &element.first->second;
}
}
}

@ -5,127 +5,31 @@
using namespace std;
namespace smtpd {
TokenVector toTokenVector(String input, size_t maxTokens) {
return util::split(input, '|', maxTokens);
}
TokenPattern::TokenPattern() :
sparse_vector<Token>() {}
String toString(TokenVector tokenVector) {
return util::join(tokenVector, '|');
}
TokenPattern::TokenPattern(const TokenPattern & pattern) :
sparse_vector<Token>(pattern), wildcard(pattern.wildcard) {};
String toString(TokenPattern pattern) {
return toString(pattern.toTokens());
}
TokenPattern::TokenPattern(const std::vector<std::string> & vector, bool wildcard) :
sparse_vector<Token>(vector), wildcard(wildcard) {}
TokenPattern::TokenPattern() {}
TokenPattern::TokenPattern(const string & input, bool wildcard) :
TokenPattern(util::split(input, '|'), wildcard) {}
TokenPattern::TokenPattern(TokenVector tokens, bool wildcard) : wildcard(wildcard) {
for(size_t index = 0; index < tokens.size(); ++index) {
auto token = tokens.at(index);
if(wildcard && token == "*") {
continue;
}
this->emplace(index, token);
}
}
TokenPattern::TokenPattern(std::initializer_list<Token> list) :
sparse_vector<Token>(list) {}
TokenPattern::TokenPattern(String input, bool wildcard) : TokenPattern(toTokenVector(input), wildcard) {}
TokenVector TokenPattern::toTokens() const {
size_t length = (--this->end())->first + 1;
TokenVector output{length};
for(size_t index = 0; index < length; ++index) {
if(count(index) == 0) {
output.at(index) = "*";
continue;
}
output.at(index) = at(index);
}
return output;
TokenPattern::operator std::string() const {
return util::join(*this, '|');
}
bool operator<(const TokenPattern& l, const TokenPattern& r) {
log::ldebug("Checking if % < %", toString(l), toString(r));
log::open();
if(l == r) {
log::ldebug("Not less becsuse left equals right");
log::close();
return false;
}
for(auto le = l.begin(); le != l.end(); ++le) {
if(r.count(le->first) == 0) {
log::ldebug("Not less because right is missing % -> %", le->first, toString(le->second));
log::close();
return false;
}
if(le->second < r.at(le->first)) {
log::ldebug("Less because % < %", toString(le->second), toString(r.at(le->first)));
log::close();
return true;
}
}
if(l.size() < r.size()) {
log::ldebug("Less because size of % < size of %", toString(l), toString(r));
log::close();
return true;
}
log::error("Less-than operator fell through!");
log::close();
throw;
bool operator<(const TokenPattern& left, const TokenPattern& right) {
return true;
}
bool operator==(const TokenPattern& l, const TokenPattern& r) {
log::ldebug("Checking if % == %", toString(l), toString(r));
log::open();
const TokenPattern *subset, *superset;
if(r.wildcard == l.wildcard) {
log::ldebug("Left and right are both (not) wildcards");
if(l.size() != r.size()) {
log::ldebug("Not equal because sizes are unequal");
log::close();
return false;
} else {
subset = &l;
superset = &r;
}
} else if(l.wildcard) {
log::ldebug("Left is wildcard");
subset = &l;
superset = &r;
} else {
log::ldebug("Right is wildcard");
subset = &r;
superset = &l;
}
for(auto le : *subset) {
if(superset->count(le.first) == 0) {
log::ldebug("Not equal because % -> % is missing in superset", le.first, toString(le.second));
log::close();
return false;
}
auto rv = superset->at(le.first);
if(rv != le.second) {
log::ldebug("Not equal because % -> % != % in superset", le.first, toString(le.second), rv);
log::close();
return false;
}
log::ldebug("% -> % == % in superset", le.first, toString(le.second), rv);
}
log::ldebug("Patterns match");
log::close();
return true;
ostream & operator<<(ostream & out, const TokenPattern & pattern) {
return out;
}
}

@ -1,9 +1,12 @@
#include <util.hpp>
#include <string>
#include <vector>
using namespace std;
namespace util {
const std::vector<string> split(const string input, const string::value_type separator, const size_t max_tokens) {
const vector<string> split(const string input, const string::value_type separator, const size_t max_tokens) {
vector<string> tokens;
for(size_t index = 0, last = -1; index != max_tokens; ++index) {
@ -27,16 +30,6 @@ namespace util {
return tokens;
}
const string join(const vector<string> strings, const string::value_type separator) {
std::string output;
for(size_t index = 0; index < strings.size(); ++index) {
output.append(strings.at(index));
if(index + 1 < strings.size()) {
output.push_back(separator);
}
}
return output;
}
const string lpad(const string input, const int length, const string str) {
string output;
for(int i = 0; i < length; ++i) {

Loading…
Cancel
Save