Current section

Files

Jump to
sippet c_src parser.cc
Raw

c_src/parser.cc

// Copyright (c) 2017 The Sippet Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Parts of this code was taken from Chromium sources:
// Copyright (c) 2011-2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <erl_nif.h>
#include <cstring>
#include <unordered_map>
#include <iostream>
#include "prtime.h"
#include "string_piece.h"
#include "tokenizer.h"
#include "string_tokenizer.h"
#include "utils.h"
namespace {
typedef ERL_NIF_TERM (*ParseFunction)(ErlNifEnv* env,
std::string::const_iterator,
std::string::const_iterator);
std::unordered_map<char, ERL_NIF_TERM> g_aliases;
std::unordered_map<ERL_NIF_TERM, ParseFunction> g_parsers;
bool MakeExistingAtom(ErlNifEnv* env, StringPiece atom_name,
ERL_NIF_TERM *atom) {
return enif_make_existing_atom_len(env, atom_name.data(), atom_name.size(),
atom, ERL_NIF_LATIN1);
}
bool MakeLowerCaseExistingAtom(ErlNifEnv* env, StringPiece name,
ERL_NIF_TERM *atom) {
std::string atom_name(name.as_string());
for (auto& c : atom_name) {
if (c == '-')
c = '_';
else
c = ToLowerASCII(c);
}
return MakeExistingAtom(env, atom_name, atom);
}
ERL_NIF_TERM MakeString(ErlNifEnv* env, StringPiece s) {
ErlNifBinary bin;
if (!enif_alloc_binary(s.size(), &bin))
return enif_make_atom(env, "no_memory");
for (size_t i = 0; i < s.size(); i++)
bin.data[i] = s[i];
return enif_make_binary(env, &bin);
}
ERL_NIF_TERM MakeLowerCaseExistingAtomOrString(ErlNifEnv* env, StringPiece name) {
ERL_NIF_TERM result;
if (!MakeLowerCaseExistingAtom(env, name, &result))
result = MakeString(env, name);
return result;
}
bool IsStatusLine(
std::string::const_iterator line_begin,
std::string::const_iterator line_end) {
return ((line_end - line_begin > 4)
&& LowerCaseEqualsASCII(
StringPiece(line_begin, line_begin + 4), "sip/"));
}
std::string::const_iterator FindLineEnd(
std::string::const_iterator begin,
std::string::const_iterator end) {
size_t i = StringPiece(begin, end).find_first_of("\r\n");
if (i == StringPiece::npos)
return end;
return begin + i;
}
ERL_NIF_TERM ParseVersion(ErlNifEnv* env,
std::string::const_iterator line_begin,
std::string::const_iterator line_end) {
Tokenizer tok(line_begin, line_end);
if ((line_end - line_begin < 3) ||
!LowerCaseEqualsASCII(
StringPiece(line_begin, line_begin + 3), "sip")) {
return enif_make_atom(env, "missing_version_spec");
}
tok.Skip(3);
tok.Skip(SIP_LWS);
if (tok.EndOfInput()
|| *tok.current() != '/') {
return enif_make_atom(env, "missing_version");
}
tok.Skip();
std::string::const_iterator major_start = tok.Skip(SIP_LWS);
tok.SkipTo('.');
tok.Skip();
std::string::const_iterator minor_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput()) {
return enif_make_atom(env, "malformed_version");
}
if (!isdigit(*major_start) || !isdigit(*minor_start)) {
return enif_make_atom(env, "malformed_version_number");
}
int major = *major_start - '0';
int minor = *minor_start - '0';
return enif_make_tuple2(env, enif_make_int(env, major),
enif_make_int(env, minor));
}
ERL_NIF_TERM ParseStatusLine(ErlNifEnv* env,
std::string::const_iterator line_begin,
std::string::const_iterator line_end) {
// Extract the version number
ERL_NIF_TERM version = ParseVersion(env, line_begin, line_end);
if (enif_is_atom(env, version)) {
return version;
}
std::string::const_iterator p = std::find(line_begin, line_end, ' ');
if (p == line_end) {
return enif_make_atom(env, "missing_status_code");
}
// Skip whitespace.
while (*p == ' ')
++p;
std::string::const_iterator code = p;
while (*p >= '0' && *p <= '9')
++p;
if (p == code) {
return enif_make_atom(env, "empty_status_code");
}
int status_code;
if (!StringToInt(StringPiece(code, p), &status_code)) {
return enif_make_atom(env, "invalid_status_code");
}
// Skip whitespace.
while (*p == ' ')
++p;
// Trim trailing whitespace.
while (line_end > p && line_end[-1] == ' ')
--line_end;
std::string reason_phrase;
if (p < line_end) {
reason_phrase.assign(p, line_end);
}
ERL_NIF_TERM status_line = enif_make_new_map(env);
enif_make_map_put(env, status_line, enif_make_atom(env, "version"), version,
&status_line);
enif_make_map_put(env, status_line, enif_make_atom(env, "status_code"),
enif_make_int(env, status_code), &status_line);
enif_make_map_put(env, status_line, enif_make_atom(env, "reason_phrase"),
MakeString(env, reason_phrase), &status_line);
return status_line;
}
ERL_NIF_TERM ParseRequestLine(ErlNifEnv* env,
std::string::const_iterator line_begin,
std::string::const_iterator line_end) {
// Skip any leading whitespace.
while (line_begin != line_end &&
(*line_begin == ' ' || *line_begin == '\t' ||
*line_begin == '\r' || *line_begin == '\n'))
++line_begin;
std::string::const_iterator method_start = line_begin;
std::string::const_iterator p = std::find(line_begin, line_end, ' ');
if (p == line_end) {
return enif_make_atom(env, "missing_method");
}
std::string method(method_start, p);
// Skip whitespace.
while (*p == ' ')
++p;
std::string::const_iterator uri_start = p;
p = std::find(p, line_end, ' ');
if (p == line_end) {
return enif_make_atom(env, "missing_uri");
}
std::string uri(uri_start, p);
// Skip whitespace.
while (*p == ' ')
++p;
// Extract the version number
ERL_NIF_TERM version = ParseVersion(env, p, line_end);
if (enif_is_atom(env, version)) {
return version;
}
ERL_NIF_TERM request_line = enif_make_new_map(env);
enif_make_map_put(env, request_line, enif_make_atom(env, "method"),
MakeLowerCaseExistingAtomOrString(env, method), &request_line);
enif_make_map_put(env, request_line, enif_make_atom(env, "request_uri"),
MakeString(env, uri), &request_line);
enif_make_map_put(env, request_line, enif_make_atom(env, "version"), version,
&request_line);
return request_line;
}
ERL_NIF_TERM ParseToken(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator token_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput()) {
return enif_make_atom(env, "empty_value");
}
return MakeString(env, StringPiece(token_start,
tok->SkipNotIn(SIP_LWS ";")));
}
ERL_NIF_TERM ParseTypeSubtype(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator type_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput()) {
// empty header is OK
return enif_make_tuple(env, 0);
}
StringPiece type(type_start, tok->SkipNotIn(SIP_LWS "/"));
if (!IsToken(type)) {
return enif_make_atom(env, "invalid_token");
}
tok->SkipTo('/');
tok->Skip();
std::string::const_iterator subtype_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput()) {
return enif_make_atom(env, "missing_subtype");
}
StringPiece subtype(subtype_start, tok->SkipNotIn(SIP_LWS ";"));
if (!IsToken(subtype)) {
return enif_make_atom(env, "invalid_token");
}
return enif_make_tuple2(env, MakeString(env, type),
MakeString(env, subtype));
}
ERL_NIF_TERM ParseParameters(ErlNifEnv* env, Tokenizer* tok) {
// TODO(balena): accept generic param such as ";token"
ERL_NIF_TERM result = enif_make_new_map(env);
if (tok->EndOfInput())
return result;
tok->SkipTo(';');
tok->Skip();
GenericParametersIterator it(tok->current(), tok->end());
while (it.GetNext()) {
enif_make_map_put(env, result, MakeString(env, it.name()),
MakeString(env, it.value()), &result);
}
return result;
}
ERL_NIF_TERM ParseAuthScheme(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator scheme_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "missing_auth_scheme");
StringPiece scheme(scheme_start, tok->SkipNotIn(SIP_LWS));
return MakeString(env, scheme);
}
ERL_NIF_TERM ParseAuthParams(ErlNifEnv* env, Tokenizer* tok) {
ERL_NIF_TERM result = enif_make_new_map(env);
NameValuePairsIterator it(tok->current(), tok->end(), ',');
while (it.GetNext()) {
enif_make_map_put(env, result, MakeString(env, it.name()),
MakeString(env, it.raw_value()), &result);
}
return result;
}
ERL_NIF_TERM ParseComment(ErlNifEnv* env, Tokenizer* tok) {
tok->SkipTo('(');
if (tok->EndOfInput())
return enif_make_atom(env, "invalid_comment");
std::string::const_iterator comment_start = tok->Skip();
std::string::const_iterator comment_end = tok->end();
int lparen = 1;
while (!tok->EndOfInput()) {
if (*tok->current() == ')') {
if (--lparen == 0) {
comment_end = tok->current();
tok->Skip();
break;
}
} else if (*tok->current() == '(') {
lparen++;
}
tok->Skip();
}
if (comment_end == tok->end())
return enif_make_atom(env, "invalid_comment");
TrimLWS(&comment_start, &comment_end);
StringPiece comment(comment_start, comment_end);
return MakeString(env, comment);
}
ERL_NIF_TERM ParseUri(ErlNifEnv* env, Tokenizer* tok) {
tok->SkipTo('<');
if (tok->EndOfInput())
return enif_make_atom(env, "invalid_uri");
std::string::const_iterator uri_start = tok->Skip();
std::string::const_iterator uri_end = tok->SkipTo('>');
if (tok->EndOfInput())
return enif_make_atom(env, "unclosed_laquot");
tok->Skip();
StringPiece uri(uri_start, uri_end);
return MakeString(env, uri);
}
ERL_NIF_TERM ParseContact(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator display_name_start, display_name_end;
StringPiece address;
tok->Skip(SIP_LWS);
if (IsQuote(*tok->current())) {
// contact-param = quoted-string LAQUOT addr-spec RAQUOT
display_name_start = tok->current();
tok->Skip();
for (; !tok->EndOfInput(); tok->Skip()) {
if (*tok->current() == '\\') {
tok->Skip();
continue;
}
if (IsQuote(*tok->current()))
break;
}
if (tok->EndOfInput())
return enif_make_atom(env, "unclosed_qstring");
display_name_end = tok->Skip();
tok->SkipTo('<');
if (tok->EndOfInput())
return enif_make_atom(env, "missing_address");
std::string::const_iterator address_start = tok->Skip();
tok->SkipTo('>');
if (tok->EndOfInput())
return enif_make_atom(env, "unclosed_laquot");
address = StringPiece(address_start, tok->current());
} else {
Tokenizer laquot(tok->current(), tok->end());
laquot.SkipTo('<');
if (!laquot.EndOfInput()) {
// contact-param = *(token LWS) LAQUOT addr-spec RAQUOT
display_name_start = tok->current();
display_name_end = laquot.current();
TrimLWS(&display_name_start, &display_name_end);
std::string::const_iterator address_start = laquot.Skip();
laquot.SkipTo('>');
if (laquot.EndOfInput())
return enif_make_atom(env, "unclosed_laquot");
address = StringPiece(address_start, laquot.current());
tok->set_current(laquot.Skip());
} else if (IsToken(tok->current(), tok->current() + 1)) {
display_name_start = display_name_end = tok->end();
std::string::const_iterator address_start = tok->current();
address = StringPiece(address_start, tok->SkipNotIn(SIP_LWS));
} else {
return enif_make_atom(env, "invalid_char_found");
}
}
std::string display_name(Unquote(display_name_start, display_name_end));
return enif_make_tuple2(env, MakeString(env, display_name),
MakeString(env, address));
}
bool ParseStar(ErlNifEnv* env, Tokenizer* tok, ERL_NIF_TERM* term) {
Tokenizer star(tok->current(), tok->end());
star.Skip(SIP_LWS);
if (star.EndOfInput())
return false;
if (*star.current() != '*')
return false;
*term = MakeString(env, "*");
return true;
}
ERL_NIF_TERM ParseWarning(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator code_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "empty_input");
StringPiece code_string(code_start, tok->SkipNotIn(SIP_LWS));
int code = 0;
if (!StringToInt(code_string, &code)
|| code < 100 || code > 999)
return enif_make_atom(env, "invalid_code");
std::string::const_iterator agent_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "empty_warn_agent");
StringPiece agent(agent_start, tok->SkipNotIn(SIP_LWS));
tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "missing_warn_text");
if (*tok->current() != '"')
return enif_make_atom(env, "invalid_warn_text");
std::string::const_iterator text_start = tok->current();
tok->Skip();
for (; !tok->EndOfInput(); tok->Skip()) {
if (*tok->current() == '\\') {
tok->Skip();
continue;
}
if (*tok->current() == '"')
break;
}
if (tok->EndOfInput())
return enif_make_atom(env, "unclosed_qstring");
std::string text(text_start, tok->Skip());
text = Unquote(text.begin(), text.end());
return enif_make_tuple3(env, enif_make_int(env, code),
MakeString(env, agent), MakeString(env, text));
}
ERL_NIF_TERM ParseVia(ErlNifEnv* env, Tokenizer* tok) {
std::string::const_iterator version_start = tok->Skip(SIP_LWS);
if ((tok->end() - tok->current() < 3)
|| !LowerCaseEqualsASCII(
StringPiece(tok->current(), tok->current() + 3), "sip"))
return enif_make_atom(env, "unknown_version");
tok->SkipTo('/');
tok->Skip();
ERL_NIF_TERM version = ParseVersion(env, version_start, tok->SkipTo('/'));
if (enif_is_atom(env, version))
return version;
tok->Skip();
std::string::const_iterator protocol_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "missing_sent_protocol");
std::string protocol(protocol_start, tok->SkipNotIn(SIP_LWS));
protocol = ToLowerASCII(protocol);
std::string::const_iterator sentby_start = tok->Skip(SIP_LWS);
if (tok->EndOfInput())
return enif_make_atom(env, "missing_sentby");
std::string::const_iterator sentby_end = tok->SkipTo(';');
TrimLWS(&sentby_start, &sentby_end);
StringPiece sentby_string(sentby_start, sentby_end);
if (sentby_string.empty())
return enif_make_atom(env, "missing_sentby");
std::string host;
int port;
if (!ParseHostAndPort(sentby_string.as_string(), &host, &port))
return enif_make_atom(env, "invalid_sentby");
if (port == -1) {
if (protocol == "udp" || protocol == "tcp")
port = 5060;
else if (protocol == "tls")
port = 5061;
else
port = 0;
}
if (host[0] == '[') // remove brackets from IPv6 addresses
host = host.substr(1, host.size()-2);
return enif_make_tuple3(env, version,
MakeLowerCaseExistingAtomOrString(env, protocol),
enif_make_tuple2(env, MakeString(env, host), enif_make_int(env, port)));
}
ERL_NIF_TERM ParseSingleToken(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
return ParseToken(env, &tok);
}
ERL_NIF_TERM ParseSingleTokenParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
ERL_NIF_TERM value = ParseToken(env, &tok);
if (enif_is_atom(env, value))
return value;
ERL_NIF_TERM parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
return enif_make_tuple2(env, value, parameters);
}
ERL_NIF_TERM ParseMultipleTokens(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
ERL_NIF_TERM token = ParseSingleToken(env, it.value_begin(),
it.value_end());
if (enif_is_atom(env, token))
return token;
result = enif_make_list_cell(env, token, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseMultipleTokenParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
ERL_NIF_TERM value = ParseSingleTokenParams(env, it.value_begin(),
it.value_end());
if (enif_is_atom(env, value))
return value;
result = enif_make_list_cell(env, value, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseSingleTypeSubtypeParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
ERL_NIF_TERM value = ParseTypeSubtype(env, &tok);
if (enif_is_atom(env, value))
return value;
ERL_NIF_TERM parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
return enif_make_tuple2(env, value, parameters);
}
ERL_NIF_TERM ParseMultipleTypeSubtypeParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
ERL_NIF_TERM value = ParseSingleTypeSubtypeParams(env, it.value_begin(),
it.value_end());
if (enif_is_atom(env, value))
return value;
result = enif_make_list_cell(env, value, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseMultipleUriParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
Tokenizer tok(it.value_begin(), it.value_end());
ERL_NIF_TERM uri = ParseUri(env, &tok);
if (enif_is_atom(env, uri))
return uri;
ERL_NIF_TERM parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
result = enif_make_list_cell(env,
enif_make_tuple2(env, uri, parameters), result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseSingleInteger(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
std::string::const_iterator token_start = tok.Skip(SIP_LWS);
std::string digits(token_start, tok.SkipNotIn(SIP_LWS));
int i = 0;
if (!StringToInt(digits, &i))
return enif_make_atom(env, "invalid_digits");
return enif_make_int(env, i);
}
ERL_NIF_TERM ParseOnlyAuthParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
return ParseAuthParams(env, &tok);
}
ERL_NIF_TERM ParseSchemeAndAuthParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
ERL_NIF_TERM scheme = ParseAuthScheme(env, &tok);
if (enif_is_atom(env, scheme))
return scheme;
ERL_NIF_TERM parameters = ParseAuthParams(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
return enif_make_list1(env,
enif_make_tuple2(env, scheme, parameters));
}
ERL_NIF_TERM ParseSingleContactParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
ERL_NIF_TERM contact = ParseContact(env, &tok);
if (enif_is_atom(env, contact))
return contact;
ERL_NIF_TERM parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
int arity;
const ERL_NIF_TERM *name_and_address;
enif_get_tuple(env, contact, &arity, &name_and_address);
return enif_make_tuple3(env, name_and_address[0], name_and_address[1],
parameters);
}
ERL_NIF_TERM ParseMultipleContactParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
ERL_NIF_TERM value = ParseSingleContactParams(env, it.value_begin(),
it.value_end());
if (enif_is_atom(env, value))
return value;
result = enif_make_list_cell(env, value, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseStarOrMultipleContactParams(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
ERL_NIF_TERM star;
if (ParseStar(env, &tok, &star)) {
return star;
} else {
return ParseMultipleContactParams(env, values_begin, values_end);
}
}
ERL_NIF_TERM ParseTrimmedUtf8(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
TrimLWS(&values_begin, &values_end);
return MakeString(env, StringPiece(values_begin, values_end));
}
ERL_NIF_TERM ParseCseq(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
std::string::const_iterator integer_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput())
return enif_make_atom(env, "missing_sequence");
std::string integer_string(integer_start, tok.SkipNotIn(SIP_LWS));
int sequence = 0;
if (!StringToInt(integer_string, &sequence))
return enif_make_atom(env, "invalid_sequence");
std::string::const_iterator method_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput())
return enif_make_atom(env, "missing_method");
StringPiece method_name(method_start, tok.SkipNotIn(SIP_LWS));
return enif_make_tuple2(env, enif_make_int(env, sequence),
MakeLowerCaseExistingAtomOrString(env, method_name));
}
ERL_NIF_TERM ParseDate(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
TrimLWS(&values_begin, &values_end);
if (values_begin == values_end)
return enif_make_atom(env, "empty_date");
PRExplodedTime result_time;
std::string time_string(StringPiece(values_begin, values_end).as_string());
PRStatus status = PR_ParseTimeStringToExplodedTime(time_string.c_str(),
PR_TRUE, &result_time);
if (PR_SUCCESS != status)
return enif_make_atom(env, "invalid_date");
PR_NormalizeTime(&result_time, &PR_GMTParameters);
ERL_NIF_TERM result = enif_make_tuple3(env,
enif_make_tuple3(env,
enif_make_int(env, result_time.tm_year),
enif_make_int(env, result_time.tm_month + 1),
enif_make_int(env, result_time.tm_mday)),
enif_make_tuple3(env,
enif_make_int(env, result_time.tm_hour),
enif_make_int(env, result_time.tm_min),
enif_make_int(env, result_time.tm_sec)),
enif_make_tuple2(env,
enif_make_int(env, result_time.tm_usec),
enif_make_int(env, result_time.tm_usec == 0 ? 0 : 5)));
return result;
}
ERL_NIF_TERM ParseTimestamp(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
std::string::const_iterator timestamp_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput())
return enif_make_atom(env, "missing_timestamp");
StringPiece timestamp_string(timestamp_start, tok.SkipNotIn(SIP_LWS));
double timestamp = .0;
if (!StringToDouble(timestamp_string, &timestamp))
return enif_make_atom(env, "invalid_timestamp");
// delay is optional
double delay = .0;
std::string::const_iterator delay_start = tok.Skip(SIP_LWS);
if (!tok.EndOfInput()) {
StringPiece delay_string(delay_start, tok.SkipNotIn(SIP_LWS));
StringToDouble(delay_string, &delay);
// ignore errors parsing the optional delay
}
return enif_make_tuple2(env, enif_make_double(env, timestamp),
enif_make_double(env, delay));
}
ERL_NIF_TERM ParseMimeVersion(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
std::string::const_iterator major_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput())
return enif_make_atom(env, "missing_major");
StringPiece major_string(major_start, tok.SkipTo('.'));
int major = 0;
if (major_string.empty()
|| !StringToInt(major_string, &major))
return enif_make_atom(env, "missing_or_invalid_major");
tok.Skip();
std::string::const_iterator minor_start = tok.Skip(SIP_LWS);
StringPiece minor_string(minor_start, tok.end());
int minor = 0;
if (minor_string.empty()
|| !StringToInt(minor_string, &minor))
return enif_make_atom(env, "invalid_minor");
return enif_make_tuple2(env, enif_make_int(env, major),
enif_make_int(env, minor));
}
ERL_NIF_TERM ParseRetryAfter(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
Tokenizer tok(values_begin, values_end);
std::string::const_iterator delta_start = tok.Skip(SIP_LWS);
if (tok.EndOfInput())
return enif_make_atom(env, "missing_delta_secs");
StringPiece delta_string(delta_start, tok.SkipNotIn(SIP_LWS "(;"));
int delta_seconds = 0;
if (delta_string.empty()
|| !StringToInt(delta_string, &delta_seconds))
return enif_make_atom(env, "missing_or_invalid_delta_secs");
ERL_NIF_TERM comment;
StringPiece remaining(tok.current(), tok.end());
if (remaining.find('(') < remaining.find(';')) {
comment = ParseComment(env, &tok);
if (enif_is_atom(env, comment))
return comment;
} else {
comment = MakeString(env, "");
}
ERL_NIF_TERM parameters;
tok.SkipTo(';');
if (!tok.EndOfInput()) {
parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
} else {
parameters = enif_make_new_map(env);
}
return enif_make_tuple3(env, enif_make_int(env, delta_seconds), comment,
parameters);
}
ERL_NIF_TERM ParseMultipleWarnings(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
Tokenizer tok(it.value_begin(), it.value_end());
ERL_NIF_TERM value = ParseWarning(env, &tok);
if (enif_is_atom(env, value))
return value;
result = enif_make_list_cell(env, value, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseMultipleVias(ErlNifEnv* env,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM result = enif_make_list(env, 0);
ValuesIterator it(values_begin, values_end, ',');
while (it.GetNext()) {
Tokenizer tok(it.value_begin(), it.value_end());
ERL_NIF_TERM value = ParseVia(env, &tok);
if (enif_is_atom(env, value))
return value;
ERL_NIF_TERM parameters = ParseParameters(env, &tok);
if (enif_is_atom(env, parameters))
return parameters;
int arity;
const ERL_NIF_TERM *via;
enif_get_tuple(env, value, &arity, &via);
ERL_NIF_TERM term = enif_make_tuple4(env, via[0], via[1], via[2],
parameters);
result = enif_make_list_cell(env, term, result);
}
enif_make_reverse_list(env, result, &result);
return result;
}
ERL_NIF_TERM ParseHeader(ErlNifEnv* env,
std::string::const_iterator name_begin,
std::string::const_iterator name_end,
std::string::const_iterator values_begin,
std::string::const_iterator values_end) {
ERL_NIF_TERM header_name_term = 0, header_values_term;
StringPiece header_name(name_begin, name_end);
StringPiece header_values(values_begin, values_end);
if (header_name.size() == 1) {
auto alias = g_aliases.find(ToLowerASCII(header_name[0]));
if (alias != g_aliases.end()) {
header_name_term = alias->second;
}
}
if (header_name_term == 0) {
MakeLowerCaseExistingAtom(env, header_name, &header_name_term);
}
auto f = g_parsers.find(header_name_term);
if (f != g_parsers.end()) {
header_values_term = (f->second)(env, values_begin, values_end);
if (enif_is_atom(env, header_values_term))
return header_values_term;
} else {
header_name_term = MakeString(env, header_name);
header_values_term = enif_make_list1(env, MakeString(env, header_values));
}
return enif_make_tuple2(env, header_name_term, header_values_term);
}
bool AssembleRawHeaders(const char* input, size_t length,
std::string *output) {
const char* line_start;
const char* line_end;
output->reserve(length);
for (size_t i = 0; i < length; i++) {
line_start = input + i;
do {
char c = *(input + i);
if (c == '\r' || c == '\n')
break;
i++;
} while (i < length);
line_end = input + i;
if (line_start != line_end)
output->append(line_start, line_end);
if (i == length)
break;
// now inspect the next character
char c = *(input + i);
if (c == '\n') {
i++; // accept single LF
} else if (c == '\r') {
i++;
if (i < length && *(input + i) == '\n')
i++; // default CRLF sequence
else
return false; // invalid CRLF sequence
}
if (i == length)
break;
if (!IsLWS(*(input + i)))
output->append(1, '\n'); // not line folding
i--; // return next character back
}
return true;
}
ERL_NIF_TERM Parse(ErlNifEnv* env, const char* raw_message, size_t length) {
std::string input;
if (!AssembleRawHeaders(raw_message, length, &input)) {
return enif_make_tuple2(env, enif_make_atom(env, "error"),
enif_make_atom(env, "invalid_line_break"));
}
ERL_NIF_TERM message = enif_make_new_map(env);
std::string::const_iterator i = input.begin();
std::string::const_iterator end = input.end();
std::string::const_iterator start = i;
ERL_NIF_TERM start_line;
i = FindLineEnd(start, end);
if (IsStatusLine(start, i)) {
start_line = ParseStatusLine(env, start, i);
} else {
start_line = ParseRequestLine(env, start, i);
}
if (enif_is_atom(env, start_line))
return start_line;
enif_make_map_put(env, message, enif_make_atom(env, "start_line"),
start_line, &message);
// Jump over next CRLF
if (i != end) {
if (*i == '\r')
++i;
if (i != end && *i == '\n')
++i;
}
HeadersIterator it(i, end, "\r\n");
ERL_NIF_TERM headers = enif_make_new_map(env);
while (it.GetNext()) {
ERL_NIF_TERM header = ParseHeader(env, it.name_begin(), it.name_end(),
it.values_begin(), it.values_end());
if (enif_is_atom(env, header))
return enif_make_tuple2(env, enif_make_atom(env, "error"),
header);
int arity;
const ERL_NIF_TERM* pair;
if (enif_get_tuple(env, header, &arity, &pair) && arity == 2) {
ERL_NIF_TERM header_name = pair[0], header_values = pair[1];
ERL_NIF_TERM adding_header_values;
if (enif_get_map_value(env, headers, header_name,
&adding_header_values)) {
if (!enif_is_list(env, adding_header_values)) {
return enif_make_tuple2(env, enif_make_atom(env, "error"),
enif_make_atom(env, "multiple_definition"));
}
enif_make_reverse_list(env, adding_header_values,
&adding_header_values);
ERL_NIF_TERM head, tail = header_values;
while (enif_get_list_cell(env, tail, &head, &tail)) {
adding_header_values = enif_make_list_cell(env, head,
adding_header_values);
}
enif_make_reverse_list(env, adding_header_values,
&adding_header_values);
} else {
adding_header_values = header_values;
}
enif_make_map_put(env, headers, header_name, adding_header_values,
&headers);
}
}
enif_make_reverse_list(env, headers, &headers);
enif_make_map_put(env, message, enif_make_atom(env, "headers"), headers,
&message);
return enif_make_tuple2(env, enif_make_atom(env, "ok"), message);
}
void LoadMethodAtoms(ErlNifEnv* env) {
#define SIP_METHOD(x) \
enif_make_atom(env, ToLowerASCII(#x).c_str());
#include "method_list.h"
#undef SIP_METHOD
}
void LoadHeaderNameAtoms(ErlNifEnv* env) {
ERL_NIF_TERM atom;
#define X(header_name, compact_name, atom_name, format) \
atom = enif_make_atom(env, #atom_name); \
g_parsers.insert(std::make_pair(atom, &Parse##format)); \
if (compact_name != 0) \
g_aliases.insert(std::make_pair(compact_name, atom));
#include "header_list.h"
#undef X
}
void LoadProtocolAtoms(ErlNifEnv* env) {
#define SIP_PROTOCOL(x) \
enif_make_atom(env, ToLowerASCII(#x).c_str());
#include "protocol_list.h"
#undef SIP_PROTOCOL
}
} // namespace
extern "C" {
static ERL_NIF_TERM parse_wrapper(ErlNifEnv* env, int argc,
const ERL_NIF_TERM argv[]) {
if (argc != 1) {
return enif_make_badarg(env);
} else if (enif_is_binary(env, argv[0])) {
ErlNifBinary bin;
enif_inspect_binary(env, argv[0], &bin);
return Parse(env, reinterpret_cast<const char*>(bin.data),
static_cast<size_t>(bin.size));
} else {
return enif_make_badarg(env);
}
}
int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
LoadMethodAtoms(env);
LoadHeaderNameAtoms(env);
LoadProtocolAtoms(env);
return 0;
}
static ErlNifFunc nif_funcs[] = {
{"parse", 1, parse_wrapper},
};
ERL_NIF_INIT(Elixir.Sippet.Parser, nif_funcs, on_load, NULL, NULL, NULL)
} // extern "C"