Throw exceptions on bad parser (parser doesnt inherently throw them.

// Copyright (c) 2009 - Decho Corp.
/* To compile to .cpp:
   ragel uri.rl -G2 -o uri.cpp
*/

#include "uri.h"

#include <sstream>

#include "ragel.h"

static const std::string unreserved("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
static const std::string sub_delims("!$&'()*+,;=");
static const std::string scheme("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.");
static const std::string userinfo("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string host("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string pchar("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@");
static const std::string path("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/");
static const std::string segment_nc("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" "@");
static const std::string query("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/?");

static std::string escape(const std::string& str, const std::string& allowedChars)
{
    const char *hexdigits = "0123456789ABCDEF";
    std::string result(str);

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while(c < end)
    {
        if (allowedChars.find(*c) != std::string::npos) {
            if (!differed) {
                result.resize(c - str.c_str());
            }
            result.append(1, '%');
            result.append(1, hexdigits[*c >> 4]);
            result.append(1, hexdigits[*c & 0xf]);
        } else {
            if (differed) {
                result.append(1, *c);
            }
        }
        ++c;
    }

    if (differed) {
        assert(result.length() > str.length());
    } else {
        assert(result == str);
    }
    return result;
}

static std::string unescape(const std::string& str)
{
    std::string result = str;

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while (c < end)
    {
        if (*c == '%') {
            assert(c + 2 < end);
            if (!differed) {
                result.resize(c - str.c_str());
            }
            char decoded;
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded = (*c - 'a' + 10) << 4;
            else if (*c >= 'A' && *c <= 'F')
                decoded = (*c - 'A' + 10) << 4;
            else {
                assert(*c >= '0' && *c <='9');
                decoded = (*c - '0') << 4;
            }
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded |= *c - 'a' + 10;
            else if (*c >= 'A' && *c <= 'F')
                decoded |= *c - 'A' + 10;
            else {
                assert(*c >= '0' && *c <='9');
                decoded |= *c - '0';
            }
            result.append(1, decoded);
        } else if (differed) {
            result.append(1, *c);
        }
        ++c;
    }
    return result;
}

%%{

    machine uri_parser;

    gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
    reserved = gen_delims | sub_delims;
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;

    action marku { mark = fpc; }
    action save_scheme
    {
        m_uri->scheme(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    scheme = (alpha | digit | "+" | "-" | ".")+ >marku %save_scheme;

    action save_port
    {
        if (fpc == mark)
            m_uri->authority.port(-1);
        else
            m_uri->authority.port(atoi(mark));
        mark = NULL;
    }
    action save_userinfo
    {
        m_uri->authority.userinfo(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_host
    {
        m_uri->authority.host(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
    dec_octet = digit | [1-9] digit | "1" digit{2} | 2 [0-4] digit | "25" [0-5];
    IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
    h16 = xdigit{1,4};
    ls32 = (h16 ":" h16) | IPv4address;
    IPv6address = (                         (h16 ":"){6} ls32) |
                  (                    "::" (h16 ":"){5} ls32) |
                  ((             h16)? "::" (h16 ":"){4} ls32) |
                  (((h16 ":"){1} h16)? "::" (h16 ":"){3} ls32) |
                  (((h16 ":"){2} h16)? "::" (h16 ":"){2} ls32) |
                  (((h16 ":"){3} h16)? "::" (h16 ":"){1} ls32) |
                  (((h16 ":"){4} h16)? "::"              ls32) |
                  (((h16 ":"){5} h16)? "::"              h16 ) |
                  (((h16 ":"){6} h16)? "::"                  );
    IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
    IP_literal = "[" (IPv6address | IPvFuture) "]";
    reg_name = (unreserved | pct_encoded | sub_delims)*;
    host = IP_literal | IPv4address | reg_name;
    port = digit*;

    authority = ( (userinfo %save_userinfo "@")? host >marku %save_host (":" port >marku %save_port)? ) >marku;

    action save_segment
    {
        m_path->segments.push_back(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    segment = pchar* >marku %save_segment;
    segment_nz = pchar+ >marku %save_segment;
    segment_nz_nc = (pchar - ":")+ >marku %save_segment;

    action set_absolute
    {
        m_path->type = URI::Path::ABSOLUTE;
    }
    action set_relative
    {
        m_path->type = URI::Path::RELATIVE;
    }

    path_abempty = ("/" segment >set_absolute)*;
    path_absolute = "/" (segment_nz ("/" segment)*)? > set_absolute;
    path_noscheme = segment_nz_nc >set_relative ("/" segment)*;
    path_rootless = segment_nz >set_relative ("/" segment)*;
    path_empty = "" %set_relative;
    path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);

    action save_query
    {
        m_uri->query(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_fragment
    {
        m_uri->fragment(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    query = (pchar | "/" | "?")* >marku %save_query;
    fragment = (pchar | "/" | "?")* >marku %save_fragment;

    hier_part = "//" authority path_abempty | path_absolute | path_rootless | path_empty;

    relative_part = "//" authority path_abempty | path_absolute | path_noscheme | path_empty;
    relative_ref = relative_part ( "?" query )? ( "#" fragment )?;

    absolute_URI = scheme ":" hier_part ( "?" query )? ;

    URI = scheme ":" hier_part ( "?" query )? ( "#" fragment )?;
    URI_reference = URI | relative_ref;
}%%

%%{
        machine uri_parser_proper;
        include uri_parser;
        main := URI_reference;
        write data;
}%%
class URIParser : public RagelParser
{
public:
    URIParser(URI& uri)
    {
        m_uri = &uri;
        m_path = &m_uri->path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
        %% write exec;
    }

public:
    bool complete() const
    {
        return cs >= uri_parser_proper_first_final;
    }

    bool error() const
    {
        return cs == uri_parser_proper_error;
    }

private:
    URI *m_uri;
    URI::Path *m_path;
};

%%{
    machine uri_path_parser;
    include uri_parser;
    main := path;
    write data;
}%%
class URIPathParser : public RagelParser
{
public:
    URIPathParser(URI::Path& path)
    {
        m_path = &path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
        %% write exec;
    }

public:
    bool complete() const
    {
        return cs >= uri_path_parser_first_final;
    }

    bool error() const
    {
        return cs == uri_path_parser_error;
    }

private:
    URI::Path *m_path;
};

URI::URI()
{
    reset();
}

URI::URI(const std::string& uri)
{
    *this = uri;
}

URI&
URI::operator=(const std::string& uri)
{
    URIParser parser(*this);
    parser.run(uri);
    if (parser.error() || !parser.complete())
        throw std::invalid_argument("uri");
    return *this;
}

void
URI::reset()
{
    schemeDefined(false);
    authority.hostDefined(false);
    path.type = Path::RELATIVE;
    path.segments.clear();
    queryDefined(false);
    fragmentDefined(false);
}

URI::Authority::Authority()
{
    userinfoDefined(false);
    hostDefined(false);
    portDefined(false);
}

void
URI::Authority::normalize(const std::string& defaultHost, bool emptyHostValid,
    int defaultPort, bool emptyPortValid)
{
    for(size_t i = 0; i < m_host.length(); ++i)
        m_host[i] = tolower(m_host[i]);
    if (m_port == defaultPort)
        m_port = -1;
    if (m_port == -1 && !emptyPortValid)
        m_portDefined = false;
    if (m_host == defaultHost)
        m_host.clear();
    if (m_host.empty() && !emptyHostValid && !m_userinfoDefined && !m_portDefined)
        m_hostDefined = false;
}

std::ostream&
operator<<(std::ostream& os, const URI::Authority& authority)
{
    assert(authority.hostDefined());
    if (authority.userinfoDefined()) {
        os << escape(authority.userinfo(), userinfo) << "@";
    }
    os << escape(authority.host(), host);
    if (authority.portDefined()) {
        os << ":";
        if (authority.port() > 0) {
            os << authority.port();
        }
    }
    return os;
}

URI::Path::Path()
{
    type = RELATIVE;
}

URI::Path::Path(const std::string& path)
{
    *this = path;
}

URI::Path&
URI::Path::operator=(const std::string& path)
{
    URIPathParser parser(*this);
    parser.run(path);
    if (parser.error() || !parser.complete())
        throw std::invalid_argument("uri");
    return *this;
}

void
URI::Path::removeDotComponents()
{
    for(int i = 0; i < segments.size(); ++i) {
        if (segments[i] == ".") {
            if (i + 1 == segments.size()) {
                segments[i].clear();
                continue;
            } else {
                segments.erase(segments.begin() + i);
                --i;
                continue;
            }
        }
        if (segments[i] == "..") {
            if (i == 0) {
                segments.erase(segments.begin());
                --i;
                continue;
            }
            if (i + 1 == segments.size()) {
                segments.resize(segments.size() - 1);
                segments.back().clear();
                --i;
                continue;
            }
            segments.erase(segments.begin() + i, segments.begin() + i + 2);
            i -= 2;
            continue;
        }
    }
}

void
URI::Path::normalize(bool emptyPathValid)
{
    removeDotComponents();
    if (segments.empty() && !emptyPathValid)
        type = ABSOLUTE;
}

void
URI::Path::merge(const Path& rhs)
{
    assert(rhs.type == RELATIVE);
    if (!segments.empty()) {
        segments.pop_back();
        segments.insert(segments.end(), rhs.segments.begin(), rhs.segments.end());
    } else {
        segments = rhs.segments;
    }
}

std::ostream&
URI::Path::serialize(std::ostream& os, bool schemeless) const
{
    if (segments.empty() && type == ABSOLUTE) {
        return os << "/";
    }
    for (size_t i = 0; i < segments.size(); ++i) {
        if (i != 0 || type == ABSOLUTE) {
            os << "/";
        }
        if (i == 0 && type == RELATIVE && schemeless) {
            os << escape(segments[i], segment_nc);
        } else {
            os << escape(segments[i], pchar);
        }
    }
    return os;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path& path)
{
    return path.serialize(os);
}

void
URI::normalize()
{
    for (size_t i = 0; i < m_scheme.size(); ++i)
        m_scheme[i] = tolower(m_scheme[i]);

    if (m_scheme == "http" || m_scheme == "https") {
        authority.normalize("", false, m_scheme.size() == 4 ? 80 : 443, false);
        path.normalize();
    } else if (m_scheme == "file") {
        authority.normalize("localhost", true);
        path.normalize();
    } else {
        authority.normalize();
        path.normalize();
    }
}

std::string
URI::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::ostream&
operator<<(std::ostream& os, const URI& uri)
{
    if (uri.schemeDefined()) {
        os << escape(uri.scheme(), scheme) << ":";
    }

    if (uri.authority.hostDefined()) {
        os << "//" << uri.authority;
    }

    if (uri.path.type == URI::Path::RELATIVE && uri.authority.hostDefined()) {
        os << "/";
    } else
    // Has scheme, but no authority, must ensure that an absolute path
    // doesn't begin with an empty segment (or could be mistaken for authority)
    if (uri.schemeDefined() && !uri.authority.hostDefined() &&
        uri.path.type == URI::Path::ABSOLUTE &&
        uri.path.segments.size() > 0 && uri.path.segments.front().empty()) {
        os << "/";
    }
    uri.path.serialize(os, !uri.schemeDefined());

    if (uri.queryDefined()) {
        os << "?" << escape(uri.query(), query);
    }

    if (uri.fragmentDefined()) {
        os << "#" << escape(uri.fragment(), query);
    }
    return os;
}

URI
URI::transform(const URI& base, const URI& relative)
{
    assert(base.schemeDefined());

    URI target;
    if (relative.schemeDefined()) {
        target.scheme(relative.scheme());
        target.authority = relative.authority;
        target.path = relative.path;
        target.path.removeDotComponents();
        target.m_query = relative.m_query;
        target.m_queryDefined = relative.m_queryDefined;
    } else {
        if (relative.authority.hostDefined()) {
            target.authority = relative.authority;
            target.path = relative.path;
            target.path.removeDotComponents();
            target.m_query = relative.m_query;
            target.m_queryDefined = relative.m_queryDefined;
        } else {
            if (relative.path.isEmpty()) {
                target.path = base.path;
                if (relative.queryDefined()) {
                    target.query(relative.query());
                } else {
                    target.m_query = base.m_query;
                    target.m_queryDefined = base.m_queryDefined;
                }
            } else {
                if (relative.path.type == Path::ABSOLUTE) {
                    target.path = relative.path;
                } else {
                    target.path = base.path;
                    target.path.merge(relative.path);
                    if (!base.authority.hostDefined())
                        target.path.type = Path::ABSOLUTE;
                }
                target.path.removeDotComponents();
                target.m_query = relative.m_query;
                target.m_queryDefined = relative.m_queryDefined;
            }
            target.authority = base.authority;
        }
        target.scheme(base.scheme());
    }
    target.m_fragment = relative.m_fragment;
    target.m_fragmentDefined = relative.m_fragmentDefined;
    return target;
}

// Copyright (c) 2009 - Decho Corp.
/* To compile to .cpp:
   ragel uri.rl -G2 -o uri.cpp
*/

#include "uri.h"

#include <sstream>

#include "ragel.h"

static const std::string unreserved("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
static const std::string sub_delims("!$&'()*+,;=");
static const std::string scheme("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.");
static const std::string userinfo("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string host("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":");
static const std::string pchar("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@");
static const std::string path("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/");
static const std::string segment_nc("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" "@");
static const std::string query("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" "!$&'()*+,;=" ":@" "/?");

static std::string escape(const std::string& str, const std::string& allowedChars)
{
    const char *hexdigits = "0123456789ABCDEF";
    std::string result(str);

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while(c < end)
    {
        if (allowedChars.find(*c) != std::string::npos) {
            if (!differed) {
                result.resize(c - str.c_str());
            }
            result.append(1, '%');
            result.append(1, hexdigits[*c >> 4]);
            result.append(1, hexdigits[*c & 0xf]);
        } else {
            if (differed) {
                result.append(1, *c);
            }
        }
        ++c;
    }

    if (differed) {
        assert(result.length() > str.length());
    } else {
		assert(result == str);
	}
    return result;
}

static std::string unescape(const std::string& str)
{
    std::string result = str;

    const char *c = str.c_str();
    const char *end = c + str.length();
    bool differed = false;
    while (c < end)
    {
        if (*c == '%') {
            assert(c + 2 < end);
            if (!differed) {
                result.resize(c - str.c_str());
            }
            char decoded;
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded = (*c - 'a' + 10) << 4;
            else if (*c >= 'A' && *c <= 'F')
                decoded = (*c - 'A' + 10) << 4;
            else {
                assert(*c >= '0' && *c <='9');
                decoded = (*c - '0') << 4;
            }
            ++c;
            if (*c >= 'a' && *c <= 'f')
                decoded |= *c - 'a' + 10;
            else if (*c >= 'A' && *c <= 'F')
                decoded |= *c - 'A' + 10;
            else {
                assert(*c >= '0' && *c <='9');
                decoded |= *c - '0';
            }
            result.append(1, decoded);
        } else if (differed) {
            result.append(1, *c);
        }
        ++c;
    }
    return result;
}

%%{

    machine uri_parser;

    gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
    sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
    reserved = gen_delims | sub_delims;
    unreserved = alpha | digit | "-" | "." | "_" | "~";
    pct_encoded = "%" xdigit xdigit;

    action marku { mark = fpc; }
    action save_scheme
    {
        m_uri->scheme(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    scheme = (alpha | digit | "+" | "-" | ".")+ >marku %save_scheme;

    action save_port
    {
        if (fpc == mark)
            m_uri->authority.port(-1);
        else
            m_uri->authority.port(atoi(mark));
        mark = NULL;
    }
    action save_userinfo
    {
        m_uri->authority.userinfo(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_host
    {
        m_uri->authority.host(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    userinfo = (unreserved | pct_encoded | sub_delims | ":")*;
    dec_octet = digit | [1-9] digit | "1" digit{2} | 2 [0-4] digit | "25" [0-5];
    IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
    h16 = xdigit{1,4};
    ls32 = (h16 ":" h16) | IPv4address;
    IPv6address = (                         (h16 ":"){6} ls32) |
                  (                    "::" (h16 ":"){5} ls32) |
                  ((             h16)? "::" (h16 ":"){4} ls32) |
                  (((h16 ":"){1} h16)? "::" (h16 ":"){3} ls32) |
                  (((h16 ":"){2} h16)? "::" (h16 ":"){2} ls32) |
                  (((h16 ":"){3} h16)? "::" (h16 ":"){1} ls32) |
                  (((h16 ":"){4} h16)? "::"              ls32) |
                  (((h16 ":"){5} h16)? "::"              h16 ) |
                  (((h16 ":"){6} h16)? "::"                  );
    IPvFuture = "v" xdigit+ "." (unreserved | sub_delims | ":")+;
    IP_literal = "[" (IPv6address | IPvFuture) "]";
    reg_name = (unreserved | pct_encoded | sub_delims)*;
    host = IP_literal | IPv4address | reg_name;
    port = digit*;

    authority = ( (userinfo %save_userinfo "@")? host >marku %save_host (":" port >marku %save_port)? ) >marku;

    action save_segment
    {
        m_path->segments.push_back(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    pchar = unreserved | pct_encoded | sub_delims | ":" | "@";
    segment = pchar* >marku %save_segment;
    segment_nz = pchar+ >marku %save_segment;
    segment_nz_nc = (pchar - ":")+ >marku %save_segment;

    action set_absolute
    {
        m_path->type = URI::Path::ABSOLUTE;
    }
    action set_relative
    {
	    m_path->type = URI::Path::RELATIVE;
    }

    path_abempty = ("/" segment >set_absolute)*;
    path_absolute = "/" (segment_nz ("/" segment)*)? > set_absolute;
    path_noscheme = segment_nz_nc >set_relative ("/" segment)*;
    path_rootless = segment_nz >set_relative ("/" segment)*;
    path_empty = "" %set_relative;
    path = (path_abempty | path_absolute | path_noscheme | path_rootless | path_empty);

    action save_query
    {
        m_uri->query(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }
    action save_fragment
    {
        m_uri->fragment(unescape(std::string(mark, fpc - mark)));
        mark = NULL;
    }

    query = (pchar | "/" | "?")* >marku %save_query;
    fragment = (pchar | "/" | "?")* >marku %save_fragment;

    hier_part = "//" authority path_abempty | path_absolute | path_rootless | path_empty;

    relative_part = "//" authority path_abempty | path_absolute | path_noscheme | path_empty;
    relative_ref = relative_part ( "?" query )? ( "#" fragment )?;

    absolute_URI = scheme ":" hier_part ( "?" query )? ;

    URI = scheme ":" hier_part ( "?" query )? ( "#" fragment )?;
    URI_reference = URI | relative_ref;
}%%

%%{
        machine uri_parser_proper;
        include uri_parser;
        main := URI_reference;
        write data;
}%%
class URIParser : public RagelParser
{
public:
    URIParser(URI& uri)
    {
        m_uri = &uri;
        m_path = &m_uri->path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
        %% write exec;
    }

public:
    bool complete() const
    {
        return cs >= uri_parser_proper_first_final;
    }

    bool error() const
    {
        return cs == uri_parser_proper_error;
    }

private:
    URI *m_uri;
    URI::Path *m_path;
};

%%{
    machine uri_path_parser;
    include uri_parser;
    main := path;
    write data;
}%%
class URIPathParser : public RagelParser
{
public:
    URIPathParser(URI::Path& path)
    {
        m_path = &path;
    }

    void init()
    {
        RagelParser::init();
        %% write init;
    }

protected:
    void exec()
    {
        %% write exec;
    }

public:
    bool complete() const
    {
        return cs >= uri_path_parser_first_final;
    }

    bool error() const
    {
        return cs == uri_path_parser_error;
    }

private:
    URI::Path *m_path;
};

URI::URI()
{
	reset();
}

URI::URI(const std::string& uri)
{
	*this = uri;
}

URI&
URI::operator=(const std::string& uri)
{
    URIParser parser(*this);
    parser.run(uri);
    return *this;
}

void
URI::reset()
{
    schemeDefined(false);
    authority.hostDefined(false);
    path.type = Path::RELATIVE;
    path.segments.clear();
    queryDefined(false);
    fragmentDefined(false);
}

URI::Authority::Authority()
{
    userinfoDefined(false);
    hostDefined(false);
    portDefined(false);
}

void
URI::Authority::normalize(const std::string& defaultHost, bool emptyHostValid,
	int defaultPort, bool emptyPortValid)
{
    for(size_t i = 0; i < m_host.length(); ++i)
		m_host[i] = tolower(m_host[i]);
    if (m_port == defaultPort)
        m_port = -1;
    if (m_port == -1 && !emptyPortValid)
        m_portDefined = false;
    if (m_host == defaultHost)
        m_host.clear();
    if (m_host.empty() && !emptyHostValid && !m_userinfoDefined && !m_portDefined)
        m_hostDefined = false;
}

std::ostream&
operator<<(std::ostream& os, const URI::Authority& authority)
{
    assert(authority.hostDefined());
    if (authority.userinfoDefined()) {
        os << escape(authority.userinfo(), userinfo) << "@";
    }
    os << escape(authority.host(), host);
    if (authority.portDefined()) {
        os << ":";
        if (authority.port() > 0) {
            os << authority.port();
        }
    }
    return os;
}

URI::Path::Path()
{
    type = RELATIVE;
}

URI::Path::Path(const std::string& path)
{
    *this = path;
}

URI::Path&
URI::Path::operator=(const std::string& path)
{
    URIPathParser parser(*this);
    parser.run(path);
    return *this;
}

void
URI::Path::removeDotComponents()
{
    for(int i = 0; i < segments.size(); ++i) {
        if (segments[i] == ".") {
            if (i + 1 == segments.size()) {
                segments[i].clear();
                continue;
            } else {
                segments.erase(segments.begin() + i);
                --i;
                continue;
            }
        }
        if (segments[i] == "..") {
            if (i == 0) {
                segments.erase(segments.begin());
                --i;
                continue;
            }
            if (i + 1 == segments.size()) {
				segments.resize(segments.size() - 1);
				segments.back().clear();
                --i;
                continue;
            }
            segments.erase(segments.begin() + i, segments.begin() + i + 2);
            i -= 2;
            continue;
        }
    }
}

void
URI::Path::normalize(bool emptyPathValid)
{
    removeDotComponents();
    if (segments.empty() && !emptyPathValid)
        type = ABSOLUTE;
}

void
URI::Path::merge(const Path& rhs)
{
    assert(rhs.type == RELATIVE);
    if (!segments.empty()) {
        segments.pop_back();
        segments.insert(segments.end(), rhs.segments.begin(), rhs.segments.end());
    } else {
        segments = rhs.segments;
    }
}

std::ostream&
URI::Path::serialize(std::ostream& os, bool schemeless) const
{
    if (segments.empty() && type == ABSOLUTE) {
        return os << "/";
    }
    for (size_t i = 0; i < segments.size(); ++i) {
        if (i != 0 || type == ABSOLUTE) {
            os << "/";
        }
        if (i == 0 && type == RELATIVE && schemeless) {
            os << escape(segments[i], segment_nc);
        } else {
            os << escape(segments[i], pchar);
        }
    }
    return os;
}

std::ostream&
operator<<(std::ostream& os, const URI::Path& path)
{
    return path.serialize(os);
}

void
URI::normalize()
{
    for (size_t i = 0; i < m_scheme.size(); ++i)
        m_scheme[i] = tolower(m_scheme[i]);

    if (m_scheme == "http" || m_scheme == "https") {
        authority.normalize("", false, m_scheme.size() == 4 ? 80 : 443, false);
        path.normalize();
    } else if (m_scheme == "file") {
        authority.normalize("localhost", true);
        path.normalize();
    } else {
        authority.normalize();
        path.normalize();
    }
}

std::string
URI::toString() const
{
    std::ostringstream os;
    os << *this;
    return os.str();
}

std::ostream&
operator<<(std::ostream& os, const URI& uri)
{
    if (uri.schemeDefined()) {
        os << escape(uri.scheme(), scheme) << ":";
    }

    if (uri.authority.hostDefined()) {
        os << "//" << uri.authority;
    }

    if (uri.path.type == URI::Path::RELATIVE && uri.authority.hostDefined()) {
        os << "/";
    } else
    // Has scheme, but no authority, must ensure that an absolute path
    // doesn't begin with an empty segment (or could be mistaken for authority)
    if (uri.schemeDefined() && !uri.authority.hostDefined() &&
        uri.path.type == URI::Path::ABSOLUTE &&
        uri.path.segments.size() > 0 && uri.path.segments.front().empty()) {
        os << "/";
    }
    uri.path.serialize(os, !uri.schemeDefined());

    if (uri.queryDefined()) {
        os << "?" << escape(uri.query(), query);
    }

    if (uri.fragmentDefined()) {
        os << "#" << escape(uri.fragment(), query);
    }
    return os;
}

URI
URI::transform(const URI& base, const URI& relative)
{
    assert(base.schemeDefined());

    URI target;
    if (relative.schemeDefined()) {
        target.scheme(relative.scheme());
        target.authority = relative.authority;
        target.path = relative.path;
        target.path.removeDotComponents();
        target.m_query = relative.m_query;
        target.m_queryDefined = relative.m_queryDefined;
    } else {
        if (relative.authority.hostDefined()) {
            target.authority = relative.authority;
            target.path = relative.path;
            target.path.removeDotComponents();
            target.m_query = relative.m_query;
            target.m_queryDefined = relative.m_queryDefined;
        } else {
            if (relative.path.isEmpty()) {
                target.path = base.path;
                if (relative.queryDefined()) {
                    target.query(relative.query());
                } else {
                    target.m_query = base.m_query;
                    target.m_queryDefined = base.m_queryDefined;
                }
            } else {
                if (relative.path.type == Path::ABSOLUTE) {
                    target.path = relative.path;
                } else {
                    target.path = base.path;
                    target.path.merge(relative.path);
                    if (!base.authority.hostDefined())
                        target.path.type = Path::ABSOLUTE;
                }
                target.path.removeDotComponents();
                target.m_query = relative.m_query;
                target.m_queryDefined = relative.m_queryDefined;
            }
            target.authority = base.authority;
        }
        target.scheme(base.scheme());
    }
    target.m_fragment = relative.m_fragment;
    target.m_fragmentDefined = relative.m_fragmentDefined;
    return target;
}


Fix telnet_escape implementation so that "\r\n" doesn't get escaped as "\r\0\r\n", which is clearly ridiculous. \r should only be escaped if \n doesn't immediately follow it, and \n should never be escaped at all.

#include <stdlib.h>
#include <string.h>
#include "anachronism.h"

#define BASE_EV(ev, t) \
  (ev).SUPER_.type = TELNET_EV_##t

#define EV_DATA(ev, text, len) {\
  BASE_EV(ev, DATA);\
  (ev).data = (text);\
  (ev).length = (len);\
}

#define EV_COMMAND(ev, cmd) {\
  BASE_EV(ev, COMMAND);\
  (ev).command = (cmd);\
}

#define EV_OPTION(ev, cmd, opt) {\
  BASE_EV(ev, OPTION);\
  (ev).command = (cmd);\
  (ev).option = (opt);\
}

#define EV_SUBNEGOTIATION(ev, act, opt) {\
  BASE_EV(ev, SUBNEGOTIATION);\
  (ev).active = (act);\
  (ev).option = (opt);\
}

#define EV_WARNING(ev, msg, pos) {\
  BASE_EV(ev, WARNING);\
  (ev).message = (msg);\
  (ev).position = (pos);\
}

struct telnet_nvt
{
  int cs; /* current Ragel state */
  const telnet_byte* p; /* current position */
  const telnet_byte* pe; /* end of current packet */
  const telnet_byte* eof; /* end-of-file marker */

  telnet_byte option_mark; /* temporary storage for a command byte */
  unsigned char options[256]; /* track the state of each subnegotiation option */

  telnet_byte* buf; /* Buffer to build up a stretch of text in. */
  size_t buflen; /* Length so far of the buffer. */

  telnet_callbacks callbacks;
  void* userdata;
  int subnegotiating;
};

%%{
  machine telnet_parser;

  access nvt->;
  variable p nvt->p;
  variable pe nvt->pe;
  variable eof nvt->eof;

  action flush_text {
    if (nvt->callbacks.on_recv && nvt->buflen > 0)
    {
      telnet_text_event ev;
      EV_DATA(ev, nvt->buf, nvt->buflen);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
      nvt->buflen = 0;
    }
  }

  action char {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
      nvt->buf[nvt->buflen++] = fc;
  }

  action basic_command {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_command_event ev;
      EV_COMMAND(ev, fc);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action option_mark {
    nvt->option_mark= fc;
  }
  action option_command {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_option_event ev;
      EV_OPTION(ev, nvt->option_mark, fc);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action subneg_command {
    nvt->option_mark = fc;
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_subnegotiation_event ev;
      EV_SUBNEGOTIATION(ev, 1, nvt->option_mark);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }
  action subneg_command_end {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_subnegotiation_event ev;
      EV_SUBNEGOTIATION(ev, 0, nvt->option_mark);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action warning_cr {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_warning_event ev;
      EV_WARNING(ev, "Invalid \\r: not followed by \\n or \\0.", fpc-data);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }
  action warning_iac {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_warning_event ev;
      EV_WARNING(ev, "IAC followed by invalid command.", fpc-data);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  include telnet_parser_common "parser_common.rl";
  write data;
}%%

telnet_nvt* telnet_new_nvt()
{
  telnet_nvt* nvt = malloc(sizeof(telnet_nvt));
  if (nvt != NULL)
  {
    memset(nvt, 0, sizeof(*nvt));
    %% write init;
  }
  return nvt;
}

void telnet_free_nvt(telnet_nvt* nvt)
{
  free(nvt);
}

telnet_error telnet_get_callbacks(telnet_nvt* nvt, telnet_callbacks** callbacks)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  *callbacks = &nvt->callbacks;
  return TELNET_E_OK;
}

telnet_error telnet_set_userdata(telnet_nvt* nvt, void* userdata)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  nvt->userdata = userdata;
  return TELNET_E_OK;
}

telnet_error telnet_get_userdata(telnet_nvt* nvt, void** userdata)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  *userdata = nvt->userdata;
  return TELNET_E_OK;
}

telnet_error telnet_recv(telnet_nvt* nvt, const telnet_byte* data, const size_t length, size_t* bytes_used)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  // Only bother saving text if it'll be used
  if (nvt->callbacks.on_recv)
  {
    // Because of how the parser translates data, a run of text is guaranteed to
    // be at most 'length' characters long. In practice it's usually less, due to
    // escaped characters (IAC IAC -> IAC) and text separated by commands.
    nvt->buf = malloc(length * sizeof(*nvt->buf));
    if (!nvt->buf)
      return -1; // unable to allocate a buffer
    nvt->buflen = 0;
  }

  nvt->p = data;
  nvt->pe = data + length;
  nvt->eof = nvt->pe;

  %% write exec;

  if (bytes_used != NULL)
    *bytes_used = nvt->p - data;

  free(nvt->buf);
  nvt->buf = NULL;
  nvt->p = nvt->pe = nvt->eof = NULL;

  return TELNET_E_OK;
}

telnet_error telnet_halt(telnet_nvt* nvt)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  // Force the parser to stop where it's at.
  if (nvt->p)
    nvt->eof = nvt->pe = nvt->p + 1;

  return TELNET_E_OK;
}

static int safe_concat(const telnet_byte* in, size_t inlen, telnet_byte* out, size_t outlen)
{
  // Copy as much as possible into the buffer.
  memcpy(out, in, (outlen < inlen) ? outlen : inlen);

  // true if everything could be copied, false otherwise
  return outlen >= inlen;
}

// Escapes any special characters in data, writing the result data to out.
// Returns -1 if not everything could be copied (and out is full).
// Otherwise returns the length of the data in out.
//
// To avoid potential -1 return values, pass in an out buffer double the length of the data buffer.
static size_t telnet_escape(const telnet_byte* data, size_t length, telnet_byte* out, size_t outsize)
{
  if (data == NULL || out == NULL)
    return 0;

  size_t outlen = 0;
  size_t left = 0;
  size_t right = 0;
  const char* seq = NULL;
  for (; right < length; ++right)
  {
    switch (data[right])
    {
      case IAC_IAC:
        seq = "\xFF\xFF";
        break;
      case '\r':
        // Only escape \r if it doesn't immediately precede \n.
        if (right + 1 >= length || data[right+1] != '\n')
        {
          seq = "\r\0";
          break;
        }
        else
          ; // NOTE: FALLTHROUGH
      default:
        continue; // Move to the next character
    }

    // Add any normal data that hasn't been added yet.
    if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += right - left;
    left = right + 1;

    // Add the escape sequence.
    if (safe_concat((const telnet_byte*)seq, 2, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += 2;
  }

  // Add any leftover normal data.
  if (left < right)
  {
    if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += right - left;
  }

  return outlen;
}

telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (!nvt->callbacks.on_send)
    return TELNET_E_OK; // immediate success since they apparently don't want the data to go anywhere

  // Due to the nature of the protocol, the most any one byte can be encoded as is two bytes.
  // Hence, the smallest buffer guaranteed to contain any input is double the length of the source.
  size_t bufsize = sizeof(telnet_byte) * length * 2;
  telnet_byte* buf = malloc(bufsize);
  if (!buf)
    return TELNET_E_ALLOC;

  bufsize = telnet_escape(data, length, buf, bufsize);
  nvt->callbacks.on_send(nvt, buf, bufsize);

  free(buf);
  buf = NULL;

  return TELNET_E_OK;
}

telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (command >= IAC_SB || command == IAC_SE)
    return TELNET_E_BAD_COMMAND; // Invalid command

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, command};
    nvt->callbacks.on_send(nvt, buf, 2);
  }

  return TELNET_E_OK;
}

telnet_error telnet_send_option(telnet_nvt* nvt, const telnet_byte command, const telnet_byte option)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (command < IAC_WILL || command > IAC_DONT)
    return TELNET_E_BAD_COMMAND; // Invalid option command

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, command, option};
    nvt->callbacks.on_send(nvt, buf, 3);
  }

  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation_start(telnet_nvt* nvt, const telnet_byte option)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, IAC_SB, option};
    nvt->callbacks.on_send(nvt, buf, 3);
  }

  nvt->subnegotiating = 1;
  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation_end(telnet_nvt* nvt)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (!nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;

  if (nvt->callbacks.on_send)
  {
    static const telnet_byte buf[] = {IAC_IAC, IAC_SE};
    nvt->callbacks.on_send(nvt, buf, 2);
  }

  nvt->subnegotiating = 0;
  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (!nvt->callbacks.on_send)
    return TELNET_E_OK;

  // length*2 is the maximum buffer size needed for an escaped string.
  // The extra five bytes are for the IAC, SB, <option>, IAC, and SE frame around the data.
  size_t bufsize = (sizeof(telnet_byte) * length * 2) + 5;
  telnet_byte* buf = malloc(bufsize);
  if (!buf)
    return TELNET_E_ALLOC;

  // Begin with IAC SB <option>
  telnet_byte iac[] = {IAC_IAC, IAC_SB, option};
  memcpy(buf, iac, 3);

  // Add the subnegotiation body
  size_t escaped_length = telnet_escape(data, length, buf+3, bufsize-3) + 3;

  // End with IAC SE
  iac[1] = IAC_SE;
  memcpy(buf+escaped_length, iac, 2);

  nvt->callbacks.on_send(nvt, buf, escaped_length + 2);

  return TELNET_E_OK;
}

#include <stdlib.h>
#include <string.h>
#include "anachronism.h"

#define BASE_EV(ev, t) \
  (ev).SUPER_.type = TELNET_EV_##t

#define EV_DATA(ev, text, len) {\
  BASE_EV(ev, DATA);\
  (ev).data = (text);\
  (ev).length = (len);\
}

#define EV_COMMAND(ev, cmd) {\
  BASE_EV(ev, COMMAND);\
  (ev).command = (cmd);\
}

#define EV_OPTION(ev, cmd, opt) {\
  BASE_EV(ev, OPTION);\
  (ev).command = (cmd);\
  (ev).option = (opt);\
}

#define EV_SUBNEGOTIATION(ev, act, opt) {\
  BASE_EV(ev, SUBNEGOTIATION);\
  (ev).active = (act);\
  (ev).option = (opt);\
}

#define EV_WARNING(ev, msg, pos) {\
  BASE_EV(ev, WARNING);\
  (ev).message = (msg);\
  (ev).position = (pos);\
}

struct telnet_nvt
{
  int cs; /* current Ragel state */
  const telnet_byte* p; /* current position */
  const telnet_byte* pe; /* end of current packet */
  const telnet_byte* eof; /* end-of-file marker */

  telnet_byte option_mark; /* temporary storage for a command byte */
  unsigned char options[256]; /* track the state of each subnegotiation option */

  telnet_byte* buf; /* Buffer to build up a stretch of text in. */
  size_t buflen; /* Length so far of the buffer. */

  telnet_callbacks callbacks;
  void* userdata;
  int subnegotiating;
};

%%{
  machine telnet_parser;

  access nvt->;
  variable p nvt->p;
  variable pe nvt->pe;
  variable eof nvt->eof;

  action flush_text {
    if (nvt->callbacks.on_recv && nvt->buflen > 0)
    {
      telnet_text_event ev;
      EV_DATA(ev, nvt->buf, nvt->buflen);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
      nvt->buflen = 0;
    }
  }

  action char {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
      nvt->buf[nvt->buflen++] = fc;
  }

  action basic_command {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_command_event ev;
      EV_COMMAND(ev, fc);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action option_mark {
    nvt->option_mark= fc;
  }
  action option_command {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_option_event ev;
      EV_OPTION(ev, nvt->option_mark, fc);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action subneg_command {
    nvt->option_mark = fc;
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_subnegotiation_event ev;
      EV_SUBNEGOTIATION(ev, 1, nvt->option_mark);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }
  action subneg_command_end {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_subnegotiation_event ev;
      EV_SUBNEGOTIATION(ev, 0, nvt->option_mark);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  action warning_cr {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_warning_event ev;
      EV_WARNING(ev, "Invalid \\r: not followed by \\n or \\0.", fpc-data);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }
  action warning_iac {
    if (nvt->callbacks.on_recv && nvt->buf != NULL)
    {
      telnet_warning_event ev;
      EV_WARNING(ev, "IAC followed by invalid command.", fpc-data);
      nvt->callbacks.on_recv(nvt, (telnet_event*)&ev);
    }
  }

  include telnet_parser_common "parser_common.rl";
  write data;
}%%

telnet_nvt* telnet_new_nvt()
{
  telnet_nvt* nvt = malloc(sizeof(telnet_nvt));
  if (nvt != NULL)
  {
    memset(nvt, 0, sizeof(*nvt));
    %% write init;
  }
  return nvt;
}

void telnet_free_nvt(telnet_nvt* nvt)
{
  free(nvt);
}

telnet_error telnet_get_callbacks(telnet_nvt* nvt, telnet_callbacks** callbacks)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  *callbacks = &nvt->callbacks;
  return TELNET_E_OK;
}

telnet_error telnet_set_userdata(telnet_nvt* nvt, void* userdata)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  nvt->userdata = userdata;
  return TELNET_E_OK;
}

telnet_error telnet_get_userdata(telnet_nvt* nvt, void** userdata)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  *userdata = nvt->userdata;
  return TELNET_E_OK;
}

telnet_error telnet_recv(telnet_nvt* nvt, const telnet_byte* data, const size_t length, size_t* bytes_used)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  // Only bother saving text if it'll be used
  if (nvt->callbacks.on_recv)
  {
    // Because of how the parser translates data, a run of text is guaranteed to
    // be at most 'length' characters long. In practice it's usually less, due to
    // escaped characters (IAC IAC -> IAC) and text separated by commands.
    nvt->buf = malloc(length * sizeof(*nvt->buf));
    if (!nvt->buf)
      return -1; // unable to allocate a buffer
    nvt->buflen = 0;
  }

  nvt->p = data;
  nvt->pe = data + length;
  nvt->eof = nvt->pe;

  %% write exec;

  if (bytes_used != NULL)
    *bytes_used = nvt->p - data;

  free(nvt->buf);
  nvt->buf = NULL;
  nvt->p = nvt->pe = nvt->eof = NULL;

  return TELNET_E_OK;
}

telnet_error telnet_halt(telnet_nvt* nvt)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;

  // Force the parser to stop where it's at.
  if (nvt->p)
    nvt->eof = nvt->pe = nvt->p + 1;

  return TELNET_E_OK;
}

static int safe_concat(const telnet_byte* in, size_t inlen, telnet_byte* out, size_t outlen)
{
  // Copy as much as possible into the buffer.
  memcpy(out, in, (outlen < inlen) ? outlen : inlen);

  // true if everything could be copied, false otherwise
  return outlen >= inlen;
}

// Escapes any special characters in data, writing the result data to out.
// Returns -1 if not everything could be copied (and out is full).
// Otherwise returns the length of the data in out.
//
// To avoid potential -1 return values, pass in an out buffer double the length of the data buffer.
static size_t telnet_escape(const telnet_byte* data, size_t length, telnet_byte* out, size_t outsize)
{
  if (data == NULL || out == NULL)
    return 0;

  size_t outlen = 0;
  size_t left = 0;
  size_t right = 0;
  const char* seq = NULL;
  for (; right < length; ++right)
  {
    switch (data[right])
    {
      case '\r':
        seq = "\r\0";
        break;
      case '\n':
        seq = "\r\n";
        break;
      case IAC_IAC:
        seq = "\xFF\xFF";
        break;
      default:
        continue; // Move to the next character
    }

    // Add any normal data that hasn't been added yet.
    if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += right - left;
    left = right + 1;

    // Add the escape sequence.
    if (safe_concat((const telnet_byte*)seq, 2, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += 2;
  }

  // Add any leftover normal data.
  if (left < right)
  {
    if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
      return -1;
    outlen += right - left;
  }

  return outlen;
}

telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (!nvt->callbacks.on_send)
    return TELNET_E_OK; // immediate success since they apparently don't want the data to go anywhere

  // Due to the nature of the protocol, the most any one byte can be encoded as is two bytes.
  // Hence, the smallest buffer guaranteed to contain any input is double the length of the source.
  size_t bufsize = sizeof(telnet_byte) * length * 2;
  telnet_byte* buf = malloc(bufsize);
  if (!buf)
    return TELNET_E_ALLOC;

  bufsize = telnet_escape(data, length, buf, bufsize);
  nvt->callbacks.on_send(nvt, buf, bufsize);

  free(buf);
  buf = NULL;

  return TELNET_E_OK;
}

telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (command >= IAC_SB || command == IAC_SE)
    return TELNET_E_BAD_COMMAND; // Invalid command

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, command};
    nvt->callbacks.on_send(nvt, buf, 2);
  }

  return TELNET_E_OK;
}

telnet_error telnet_send_option(telnet_nvt* nvt, const telnet_byte command, const telnet_byte option)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (command < IAC_WILL || command > IAC_DONT)
    return TELNET_E_BAD_COMMAND; // Invalid option command

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, command, option};
    nvt->callbacks.on_send(nvt, buf, 3);
  }

  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation_start(telnet_nvt* nvt, const telnet_byte option)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;

  if (nvt->callbacks.on_send)
  {
    const telnet_byte buf[] = {IAC_IAC, IAC_SB, option};
    nvt->callbacks.on_send(nvt, buf, 3);
  }

  nvt->subnegotiating = 1;
  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation_end(telnet_nvt* nvt)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (!nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;

  if (nvt->callbacks.on_send)
  {
    static const telnet_byte buf[] = {IAC_IAC, IAC_SE};
    nvt->callbacks.on_send(nvt, buf, 2);
  }

  nvt->subnegotiating = 0;
  return TELNET_E_OK;
}

telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length)
{
  if (!nvt)
    return TELNET_E_BAD_NVT;
  else if (nvt->subnegotiating)
    return TELNET_E_SUBNEGOTIATING;
  else if (!nvt->callbacks.on_send)
    return TELNET_E_OK;

  // length*2 is the maximum buffer size needed for an escaped string.
  // The extra five bytes are for the IAC, SB, <option>, IAC, and SE frame around the data.
  size_t bufsize = (sizeof(telnet_byte) * length * 2) + 5;
  telnet_byte* buf = malloc(bufsize);
  if (!buf)
    return TELNET_E_ALLOC;

  // Begin with IAC SB <option>
  telnet_byte iac[] = {IAC_IAC, IAC_SB, option};
  memcpy(buf, iac, 3);

  // Add the subnegotiation body
  size_t escaped_length = telnet_escape(data, length, buf+3, bufsize-3) + 3;

  // End with IAC SE
  iac[1] = IAC_SE;
  memcpy(buf+escaped_length, iac, 2);

  nvt->callbacks.on_send(nvt, buf, escaped_length + 2);

  return TELNET_E_OK;
}


Accept no command line arguments without error.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "cfg.h"
#include "ncron.h"
#include "nk/log.h"
#include "nk/privilege.h"
#include "nk/copy_cmdarg.h"

struct cfgparse {
   char buf[MAX_BUF];
   size_t buflen;
   int ternary; // = 0 nothing, -1 = false, +1 = true
   int cs;
};

%%{
    machine cfg_actions;
    access ccfg.;

    action clear {
        memset(&ccfg.buf, 0, sizeof ccfg.buf);
        ccfg.buflen = 0;
        ccfg.ternary = 0;
    }
    action append {
        if (ccfg.buflen < sizeof ccfg.buf - 1)
            ccfg.buf[ccfg.buflen++] = *p;
        else
            suicide("line or option is too long");
    }
    action term {
        if (ccfg.buflen < sizeof ccfg.buf)
            ccfg.buf[ccfg.buflen] = 0;
    }
    action truval { ccfg.ternary = 1; }
    action falsval { ccfg.ternary = -1; }

    action background {
        switch (ccfg.ternary) {
        case 1: gflags_detach = 1; break;
        case -1: gflags_detach = 0; default: break;
        }
    }
    action sleep {
        g_initial_sleep = atoi(optarg);
        if (g_initial_sleep < 0)
            g_initial_sleep = 0;
        break;
    }
    action noexecsave {
        switch (ccfg.ternary) {
        case 1: g_ncron_execmode = 2; default: break;
        }
    }
    action journal {
        switch (ccfg.ternary) {
        case 1: g_ncron_execmode = 1; default: break;
        }
    }
    action crontabfile {
        copy_cmdarg(g_ncron_conf, ccfg.buf, sizeof g_ncron_conf, "conf");
    }
    action historyfile {
        copy_cmdarg(g_ncron_execfile, ccfg.buf, sizeof g_ncron_execfile,
                    "history");
    }
    action pidfile {
        copy_cmdarg(g_ncron_pidfile, ccfg.buf, sizeof g_ncron_pidfile,
                    "pidfile");
    }
    action quiet {
        switch (ccfg.ternary) {
        case 1: gflags_quiet = 1; default: break;
        }
    }
    action version { print_version(); exit(EXIT_SUCCESS); }
    action help { show_usage(); exit(EXIT_SUCCESS); }
}%%

%%{
    machine file_cfg;
    access ccfg.;
    include cfg_actions;

    spc = [ \t];
    delim = spc* '=' spc*;
    string = [^\n]+ >clear $append %term;
    term = '\n';
    value = delim string term;
    truval = ('true'|'1') % truval;
    falsval = ('false'|'0') % falsval;
    boolval = delim (truval|falsval) term;

    blankline = term;

    background = 'background' boolval @background;
    sleep = 'sleep' value @sleep;
    noexecsave = 'noexecsave' boolval @noexecsave;
    journal = 'journal' boolval @journal;
    crontabfile = 'crontab' value @crontabfile;
    historyfile = 'history' value @historyfile;
    pidfile = 'pidfile' value @pidfile;
    quiet = 'quiet' boolval @quiet;

    main := blankline |
        background | sleep | noexecsave | journal | crontabfile | historyfile |
        pidfile | quiet
    ;
}%%

%% write data;

static void parse_cfgfile(const char *fname)
{
    struct cfgparse ccfg;
    memset(&ccfg, 0, sizeof ccfg);
    FILE *f = fopen(fname, "r");
    if (!f)
        suicide("Unable to open config file '%s'.", fname);
    char l[MAX_BUF];
    size_t linenum = 0;
    while (linenum++, fgets(l, sizeof l, f)) {
        size_t llen = strlen(l);
        const char *p = l;
        const char *pe = l + llen;
        %% write init;
        %% write exec;

        if (ccfg.cs == file_cfg_error)
            suicide("error parsing config file line %zu: malformed", linenum);
        if (ccfg.cs < file_cfg_first_final)
            suicide("error parsing config file line %zu: incomplete", linenum);
    }
    fclose(f);
}

%%{
    machine cmd_cfg;
    access ccfg.;
    include cfg_actions;

    action cfgfile { parse_cfgfile(ccfg.buf); }
    action tbv { ccfg.ternary = 1; }

    string = [^\0]+ >clear $append %term;
    argval = 0 string 0;
    tbv = 0 % tbv;

    cfgfile = ('-c'|'--config') argval @cfgfile;
    background = ('-b'|'--background') tbv @background;
    sleep = ('-s'|'--sleep') argval @sleep;
    noexecsave = ('-0'|'--noexecsave') tbv @noexecsave;
    journal = ('-j'|'--journal') tbv @journal;
    crontabfile = ('-t'|'--crontab') argval @crontabfile;
    historyfile = ('-H'|'--history') argval @historyfile;
    pidfile = ('-p'|'--pidfile') argval @pidfile;
    quiet = ('-q'|'--quiet') tbv @quiet;
    version = ('-v'|'--version') 0 @version;
    help = ('-h'|'--help') 0 @help;

    main := (
        cfgfile | background | sleep | noexecsave | journal | crontabfile |
        historyfile | pidfile | quiet | version | help
    )*;
}%%

%% write data;

void parse_cmdline(int argc, char *argv[])
{
    char argb[8192];
    size_t argbl = 0;
    for (size_t i = 1; i < (size_t)argc; ++i) {
        ssize_t snl;
        if (i > 1)
            snl = snprintf(argb + argbl, sizeof argb - argbl, "%c%s",
                           0, argv[i]);
        else
            snl = snprintf(argb + argbl, sizeof argb - argbl, "%s", argv[i]);
        if (snl < 0 || (size_t)snl >= sizeof argb)
            suicide("error parsing command line option: option too long");
        argbl += snl;
    }
    if (argbl == 0)
        return;
    struct cfgparse ccfg;
    memset(&ccfg, 0, sizeof ccfg);
    const char *p = argb;
    const char *pe = argb + argbl + 1;
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (ccfg.cs == cmd_cfg_error)
        suicide("error parsing command line option: malformed");
    if (ccfg.cs >= cmd_cfg_first_final)
        return;
    suicide("error parsing command line option: incomplete");
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "cfg.h"
#include "ncron.h"
#include "nk/log.h"
#include "nk/privilege.h"
#include "nk/copy_cmdarg.h"

struct cfgparse {
   char buf[MAX_BUF];
   size_t buflen;
   int ternary; // = 0 nothing, -1 = false, +1 = true
   int cs;
};

%%{
    machine cfg_actions;
    access ccfg.;

    action clear {
        memset(&ccfg.buf, 0, sizeof ccfg.buf);
        ccfg.buflen = 0;
        ccfg.ternary = 0;
    }
    action append {
        if (ccfg.buflen < sizeof ccfg.buf - 1)
            ccfg.buf[ccfg.buflen++] = *p;
        else
            suicide("line or option is too long");
    }
    action term {
        if (ccfg.buflen < sizeof ccfg.buf)
            ccfg.buf[ccfg.buflen] = 0;
    }
    action truval { ccfg.ternary = 1; }
    action falsval { ccfg.ternary = -1; }

    action background {
        switch (ccfg.ternary) {
        case 1: gflags_detach = 1; break;
        case -1: gflags_detach = 0; default: break;
        }
    }
    action sleep {
        g_initial_sleep = atoi(optarg);
        if (g_initial_sleep < 0)
            g_initial_sleep = 0;
        break;
    }
    action noexecsave {
        switch (ccfg.ternary) {
        case 1: g_ncron_execmode = 2; default: break;
        }
    }
    action journal {
        switch (ccfg.ternary) {
        case 1: g_ncron_execmode = 1; default: break;
        }
    }
    action crontabfile {
        copy_cmdarg(g_ncron_conf, ccfg.buf, sizeof g_ncron_conf, "conf");
    }
    action historyfile {
        copy_cmdarg(g_ncron_execfile, ccfg.buf, sizeof g_ncron_execfile,
                    "history");
    }
    action pidfile {
        copy_cmdarg(g_ncron_pidfile, ccfg.buf, sizeof g_ncron_pidfile,
                    "pidfile");
    }
    action quiet {
        switch (ccfg.ternary) {
        case 1: gflags_quiet = 1; default: break;
        }
    }
    action version { print_version(); exit(EXIT_SUCCESS); }
    action help { show_usage(); exit(EXIT_SUCCESS); }
}%%

%%{
    machine file_cfg;
    access ccfg.;
    include cfg_actions;

    spc = [ \t];
    delim = spc* '=' spc*;
    string = [^\n]+ >clear $append %term;
    term = '\n';
    value = delim string term;
    truval = ('true'|'1') % truval;
    falsval = ('false'|'0') % falsval;
    boolval = delim (truval|falsval) term;

    blankline = term;

    background = 'background' boolval @background;
    sleep = 'sleep' value @sleep;
    noexecsave = 'noexecsave' boolval @noexecsave;
    journal = 'journal' boolval @journal;
    crontabfile = 'crontab' value @crontabfile;
    historyfile = 'history' value @historyfile;
    pidfile = 'pidfile' value @pidfile;
    quiet = 'quiet' boolval @quiet;

    main := blankline |
        background | sleep | noexecsave | journal | crontabfile | historyfile |
        pidfile | quiet
    ;
}%%

%% write data;

static void parse_cfgfile(const char *fname)
{
    struct cfgparse ccfg;
    memset(&ccfg, 0, sizeof ccfg);
    FILE *f = fopen(fname, "r");
    if (!f)
        suicide("Unable to open config file '%s'.", fname);
    char l[MAX_BUF];
    size_t linenum = 0;
    while (linenum++, fgets(l, sizeof l, f)) {
        size_t llen = strlen(l);
        const char *p = l;
        const char *pe = l + llen;
        %% write init;
        %% write exec;

        if (ccfg.cs == file_cfg_error)
            suicide("error parsing config file line %zu: malformed", linenum);
        if (ccfg.cs < file_cfg_first_final)
            suicide("error parsing config file line %zu: incomplete", linenum);
    }
    fclose(f);
}

%%{
    machine cmd_cfg;
    access ccfg.;
    include cfg_actions;

    action cfgfile { parse_cfgfile(ccfg.buf); }
    action tbv { ccfg.ternary = 1; }

    string = [^\0]+ >clear $append %term;
    argval = 0 string 0;
    tbv = 0 % tbv;

    cfgfile = ('-c'|'--config') argval @cfgfile;
    background = ('-b'|'--background') tbv @background;
    sleep = ('-s'|'--sleep') argval @sleep;
    noexecsave = ('-0'|'--noexecsave') tbv @noexecsave;
    journal = ('-j'|'--journal') tbv @journal;
    crontabfile = ('-t'|'--crontab') argval @crontabfile;
    historyfile = ('-H'|'--history') argval @historyfile;
    pidfile = ('-p'|'--pidfile') argval @pidfile;
    quiet = ('-q'|'--quiet') tbv @quiet;
    version = ('-v'|'--version') 0 @version;
    help = ('-h'|'--help') 0 @help;

    main := (
        cfgfile | background | sleep | noexecsave | journal | crontabfile |
        historyfile | pidfile | quiet | version | help
    )*;
}%%

%% write data;

void parse_cmdline(int argc, char *argv[])
{
    char argb[8192];
    size_t argbl = 0;
    for (size_t i = 1; i < (size_t)argc; ++i) {
        ssize_t snl;
        if (i > 1)
            snl = snprintf(argb + argbl, sizeof argb - argbl, "%c%s",
                           0, argv[i]);
        else
            snl = snprintf(argb + argbl, sizeof argb - argbl, "%s", argv[i]);
        if (snl < 0 || (size_t)snl >= sizeof argb)
            suicide("error parsing command line option: option too long");
        argbl += snl;
    }
    struct cfgparse ccfg;
    memset(&ccfg, 0, sizeof ccfg);
    const char *p = argb;
    const char *pe = argb + argbl + 1;
    const char *eof = pe;

    %% write init;
    %% write exec;

    if (ccfg.cs == cmd_cfg_error)
        suicide("error parsing command line option: malformed");
    if (ccfg.cs >= cmd_cfg_first_final)
        return;
    suicide("error parsing command line option: incomplete");
}