1
0
Fork 0
arangodb/lib/Rest/HttpRequest.cpp

1543 lines
42 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief http request
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2014 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
/// @author Achim Brandt
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
/// @author Copyright 2008-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "HttpRequest.h"
#include "Basics/conversions.h"
#include "Basics/logging.h"
#include "Basics/StringBuffer.h"
#include "Basics/StringUtils.h"
#include "Basics/tri-strings.h"
#include "Basics/Utf8Helper.h"
#include <velocypack/Builder.h>
#include <velocypack/Options.h>
#include <velocypack/Parser.h>
#include <velocypack/velocypack-aliases.h>
using namespace std;
using namespace triagens::basics;
using namespace triagens::rest;
// -----------------------------------------------------------------------------
// --SECTION-- local constants
// -----------------------------------------------------------------------------
static char const* EMPTY_STR = "";
// -----------------------------------------------------------------------------
// --SECTION-- class HttpRequest
// -----------------------------------------------------------------------------
int32_t const HttpRequest::MinCompatibility = 10300L;
std::string const HttpRequest::BatchContentType = "application/x-arango-batchpart";
std::string const HttpRequest::MultiPartContentType = "multipart/form-data";
// -----------------------------------------------------------------------------
// --SECTION-- constructors and destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief http request constructor
////////////////////////////////////////////////////////////////////////////////
HttpRequest::HttpRequest (ConnectionInfo const& info,
char const* header,
size_t length,
int32_t defaultApiCompatibility,
bool allowMethodOverride)
: _requestPath(EMPTY_STR),
_headers(5),
_values(10),
_arrayValues(1),
_cookies(1),
_contentLength(0),
_body(nullptr),
_bodySize(0),
_freeables(),
_connectionInfo(info),
_type(HTTP_REQUEST_ILLEGAL),
_prefix(),
_suffix(),
_version(HTTP_UNKNOWN),
_databaseName(),
_user(),
_requestContext(nullptr),
_defaultApiCompatibility(defaultApiCompatibility),
_isRequestContextOwner(false),
_allowMethodOverride(allowMethodOverride),
_clientTaskId(0) {
// copy request - we will destroy/rearrange the content to compute the
// headers and values in-place
char* request = TRI_DuplicateString2Z(TRI_UNKNOWN_MEM_ZONE, header, length);
if (request != nullptr) {
_freeables.emplace_back(request);
parseHeader(request, length);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destructor
////////////////////////////////////////////////////////////////////////////////
HttpRequest::~HttpRequest () {
basics::Dictionary< vector<char const*>* >::KeyValue const* begin;
basics::Dictionary< vector<char const*>* >::KeyValue const* end;
for (_arrayValues.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
vector<char const*>* v = begin->_value;
delete v;
}
for (auto& it : _freeables) {
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, it);
}
if (_requestContext != nullptr && _isRequestContextOwner) {
// only delete if we are the owner of the context
delete _requestContext;
}
}
// -----------------------------------------------------------------------------
// --SECTION-- public methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::requestPath () const {
return _requestPath;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::write (TRI_string_buffer_t* buffer) const {
string&& method = translateMethod(_type);
TRI_AppendString2StringBuffer(buffer, method.c_str(), method.size());
TRI_AppendCharStringBuffer(buffer, ' ');
// do NOT url-encode the path, we need to distingush between
// "/document/a/b" and "/document/a%2fb"
TRI_AppendStringStringBuffer(buffer, _requestPath);
// generate the request parameters
basics::Dictionary<char const*>::KeyValue const* begin;
basics::Dictionary<char const*>::KeyValue const* end;
bool first = true;
for (_values.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
if (first) {
TRI_AppendCharStringBuffer(buffer, '?');
first = false;
}
else {
TRI_AppendCharStringBuffer(buffer, '&');
}
char const* value = begin->_value;
TRI_AppendUrlEncodedStringStringBuffer(buffer, key);
TRI_AppendCharStringBuffer(buffer, '=');
TRI_AppendUrlEncodedStringStringBuffer(buffer, value);
}
TRI_AppendString2StringBuffer(buffer, " HTTP/1.1\r\n", 11);
// generate the header fields
for (_headers.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
size_t const keyLength = strlen(key);
if (keyLength == 14 && memcmp(key, "content-length", keyLength) == 0) {
continue;
}
TRI_AppendString2StringBuffer(buffer, key, keyLength);
TRI_AppendString2StringBuffer(buffer, ": ", 2);
char const* value = begin->_value;
TRI_AppendStringStringBuffer(buffer, value);
TRI_AppendString2StringBuffer(buffer, "\r\n", 2);
}
first = true;
for (_cookies.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
if (first) {
first = false;
TRI_AppendString2StringBuffer(buffer, "Cookie: ", 8);
}
else {
TRI_AppendString2StringBuffer(buffer, "; ", 2);
}
size_t const keyLength = strlen(key);
TRI_AppendString2StringBuffer(buffer, key, keyLength);
TRI_AppendString2StringBuffer(buffer, "=", 2);
char const* value = begin->_value;
TRI_AppendUrlEncodedStringStringBuffer(buffer, value);
}
TRI_AppendString2StringBuffer(buffer, "content-length: ", 16);
TRI_AppendInt64StringBuffer(buffer, _contentLength);
TRI_AppendString2StringBuffer(buffer, "\r\n\r\n", 4);
if (_body != nullptr && 0 < _bodySize) {
TRI_AppendString2StringBuffer(buffer, _body, _bodySize);
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
int64_t HttpRequest::contentLength () const {
return _contentLength;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::header (char const* key) const {
Dictionary<char const*>::KeyValue const* kv = _headers.lookup(key);
if (kv == nullptr) {
return EMPTY_STR;
}
else {
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::header (char const* key, bool& found) const {
Dictionary<char const*>::KeyValue const* kv = _headers.lookup(key);
if (kv == nullptr) {
found = false;
return EMPTY_STR;
}
else {
found = true;
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
map<string, string> HttpRequest::headers () const {
basics::Dictionary<char const*>::KeyValue const* begin;
basics::Dictionary<char const*>::KeyValue const* end;
map<string, string> result;
for (_headers.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
result[key] = begin->_value;
}
result["content-length"] = StringUtils::itoa(_contentLength);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::value (char const* key) const {
Dictionary<char const*>::KeyValue const* kv = _values.lookup(key);
if (kv == nullptr) {
return EMPTY_STR;
}
else {
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::value (char const* key, bool& found) const {
Dictionary<char const*>::KeyValue const* kv = _values.lookup(key);
if (kv == nullptr) {
found = false;
return EMPTY_STR;
}
else {
found = true;
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
map<string, string> HttpRequest::values () const {
basics::Dictionary<char const*>::KeyValue const* begin;
basics::Dictionary<char const*>::KeyValue const* end;
map<string, string> result;
for (_values.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
result[key] = begin->_value;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
map<string, vector<char const*>* > HttpRequest::arrayValues () const {
basics::Dictionary< vector<char const*>* >::KeyValue const* begin;
basics::Dictionary< vector<char const*>* >::KeyValue const* end;
map<string, vector<char const*>* > result;
for (_arrayValues.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
result[key] = begin->_value;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::cookieValue (char const* key) const {
Dictionary<char const*>::KeyValue const* kv = _cookies.lookup(key);
if (kv == nullptr) {
return EMPTY_STR;
}
else {
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::cookieValue (char const* key, bool& found) const {
Dictionary<char const*>::KeyValue const* kv = _cookies.lookup(key);
if (kv == nullptr) {
found = false;
return EMPTY_STR;
}
else {
found = true;
return kv->_value;
}
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
map<string, string> HttpRequest::cookieValues () const {
basics::Dictionary<char const*>::KeyValue const* begin;
basics::Dictionary<char const*>::KeyValue const* end;
map<string, string> result;
for (_cookies.range(begin, end); begin < end; ++begin) {
char const* key = begin->_key;
if (key == nullptr) {
continue;
}
result[key] = begin->_value;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::body () const {
return _body == nullptr ? EMPTY_STR : _body;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
size_t HttpRequest::bodySize () const {
return _bodySize;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
int HttpRequest::setBody (char const* newBody,
size_t length) {
_body = TRI_DuplicateString2Z(TRI_UNKNOWN_MEM_ZONE, newBody, length);
if (_body == nullptr) {
return TRI_ERROR_OUT_OF_MEMORY;
}
_freeables.push_back(_body);
_contentLength = (int64_t) length;
_bodySize = length;
return TRI_ERROR_NO_ERROR;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets a header field
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setHeader (char const* key,
size_t keyLength,
char const* value) {
if (keyLength == 14 && memcmp(key, "content-length", keyLength) == 0) { // 14 = strlen("content-length")
_contentLength = TRI_Int64String(value);
}
else if (keyLength == 6 && memcmp(key, "cookie", keyLength) == 0) { // 6 = strlen("cookie")
parseCookies(value);
}
else {
if (_allowMethodOverride &&
keyLength >= 13 &&
*key == 'x' && *(key + 1) == '-') {
// handle x-... headers
// override HTTP method?
if ((keyLength == 13 && memcmp(key, "x-http-method", keyLength) == 0) ||
(keyLength == 17 && memcmp(key, "x-method-override", keyLength) == 0) ||
(keyLength == 22 && memcmp(key, "x-http-method-override", keyLength) == 0)) {
string overriddenType(value);
StringUtils::tolowerInPlace(&overriddenType);
_type = getRequestType(overriddenType.c_str(), overriddenType.size());
// don't insert this header!!
return;
}
}
_headers.insert(key, keyLength, value);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the request body as VPackBuilder
////////////////////////////////////////////////////////////////////////////////
VPackBuilder HttpRequest::toVelocyPack () {
VPackOptions options;
options.checkAttributeUniqueness = true;
VPackParser parser(&options);
parser.parse(body());
return parser.steal();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the request body as TRI_json_t*
////////////////////////////////////////////////////////////////////////////////
TRI_json_t* HttpRequest::toJson (char** errmsg) {
return TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, body(), errmsg);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief determine version compatibility
////////////////////////////////////////////////////////////////////////////////
int32_t HttpRequest::compatibility () {
int32_t result = _defaultApiCompatibility;
bool found;
char const* apiVersion = header("x-arango-version", found);
if (! found) {
return result;
}
char const* p = apiVersion;
// read major version
while (*p >= '0' && *p <= '9') {
++p;
}
if ((*p == '.' || *p == '-' || *p == '\0') && p != apiVersion) {
int32_t major = TRI_Int32String2(apiVersion, (p - apiVersion));
if (major >= 10000) {
// version specified as "10400"
if (*p == '\0') {
result = major;
if (result < MinCompatibility) {
result = MinCompatibility;
}
else {
// set patch-level to 0
result /= 100L;
result *= 100L;
}
return result;
}
}
apiVersion = ++p;
// read minor version
while (*p >= '0' && *p <= '9') {
++p;
}
if ((*p == '.' || *p == '-' || *p == '\0') && p != apiVersion) {
int32_t minor = TRI_Int32String2(apiVersion, (p - apiVersion));
result = (int32_t) (minor * 100L + major * 10000L);
}
}
if (result < MinCompatibility) {
// minimum value
result = MinCompatibility;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the protocol
////////////////////////////////////////////////////////////////////////////////
const string& HttpRequest::protocol () const {
return _protocol;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the connection info
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setProtocol (const string& protocol) {
_protocol = protocol;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the connection info
////////////////////////////////////////////////////////////////////////////////
ConnectionInfo const& HttpRequest::connectionInfo () const {
return _connectionInfo;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the connection info
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setConnectionInfo (ConnectionInfo const& info) {
_connectionInfo = info;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the http request type
////////////////////////////////////////////////////////////////////////////////
HttpRequest::HttpRequestType HttpRequest::requestType () const {
return _type;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the http request type
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setRequestType (HttpRequestType newType) {
_type = newType;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the database name
////////////////////////////////////////////////////////////////////////////////
string const& HttpRequest::databaseName () const {
return _databaseName;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the authenticated user
////////////////////////////////////////////////////////////////////////////////
string const& HttpRequest::user () const {
return _user;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the authenticated user
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setUser (string const& user) {
_user = user;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the path of the request
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setRequestPath (char const* path) {
_requestPath = path;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief gets the client task id
////////////////////////////////////////////////////////////////////////////////
uint64_t HttpRequest::clientTaskId () const {
return _clientTaskId;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the client task id
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setClientTaskId (uint64_t id) {
_clientTaskId = id;
}
// -----------------------------------------------------------------------------
// --SECTION-- private methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief determine the header type
////////////////////////////////////////////////////////////////////////////////
HttpRequest::HttpRequestType HttpRequest::getRequestType (const char* ptr,
const size_t length) {
switch (length) {
case 3:
if (ptr[0] == 'g' && ptr[1] == 'e' && ptr[2] == 't') {
return HTTP_REQUEST_GET;
}
if (ptr[0] == 'p' && ptr[1] == 'u' && ptr[2] == 't') {
return HTTP_REQUEST_PUT;
}
break;
case 4:
if (ptr[0] == 'p' && ptr[1] == 'o' && ptr[2] == 's' && ptr[3] == 't') {
return HTTP_REQUEST_POST;
}
if (ptr[0] == 'h' && ptr[1] == 'e' && ptr[2] == 'a' && ptr[3] == 'd') {
return HTTP_REQUEST_HEAD;
}
break;
case 5:
if (ptr[0] == 'p' && ptr[1] == 'a' && ptr[2] == 't' && ptr[3] == 'c' && ptr[4] == 'h') {
return HTTP_REQUEST_PATCH;
}
break;
case 6:
if (ptr[0] == 'd' && ptr[1] == 'e' && ptr[2] == 'l' && ptr[3] == 'e' && ptr[4] == 't' && ptr[5] == 'e') {
return HTTP_REQUEST_DELETE;
}
break;
case 7:
if (ptr[0] == 'o' && ptr[1] == 'p' && ptr[2] == 't' && ptr[3] == 'i' && ptr[4] == 'o' && ptr[5] == 'n' && ptr[6] == 's') {
return HTTP_REQUEST_OPTIONS;
}
break;
}
return HTTP_REQUEST_ILLEGAL;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief parses the http header
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::parseHeader (char* ptr, size_t length) {
char* start = ptr;
char* end = start + length;
size_t const versionLength = strlen("http/1.x");
// current line number
int lineNum = 0;
// begin and end of current line
char* lineBegin = nullptr;
// split request
while (start < end) {
lineBegin = start;
// .............................................................................
// FIRST LINE
// .............................................................................
// check for request type (GET/POST in line 0), path and parameters
if (lineNum == 0) {
// split line at space
char* e = lineBegin;
for (; e < end && *e != ' ' && *e != '\n'; ++e) {
*e = ::tolower(*e);
}
// store key and value
char* keyBegin = lineBegin;
char* keyEnd = e;
char* valueBegin = nullptr;
char* valueEnd = nullptr;
// we found a space (*end is '\0')
if (*e == ' ') {
// extract the value
keyEnd = e;
// trim value from the start
while (e < end && *e == ' ') {
++e;
}
*keyEnd = '\0';
// there is no value at all
if (e == end) {
valueBegin = valueEnd = keyEnd;
start = end;
}
// there is only a NL
else if (*e == '\n') {
valueBegin = valueEnd = keyEnd;
start = e + 1;
}
// find space
else {
valueBegin = e;
while (e < end && *e != '\n' && *e != ' ') {
++e;
}
if (e == end) {
valueEnd = e;
start = end;
}
else {
if (*e == '\n') {
valueEnd = e;
start = e + 1;
// skip \r
if (valueBegin < valueEnd && valueEnd[-1] == '\r') {
--valueEnd;
}
}
else {
valueEnd = e;
// HTTP protocol version is expected next
// trim value
while (e < end && *e == ' ') {
++e;
}
if ((size_t)(end - e) > versionLength) {
if ((e[0] == 'h' || e[0] == 'H') &&
(e[1] == 't' || e[1] == 'T') &&
(e[2] == 't' || e[2] == 'T') &&
(e[3] == 'p' || e[3] == 'P') &&
e[4] == '/' &&
e[5] == '1' &&
e[6] == '.') {
if (e[7] == '1') {
_version = HTTP_1_1;
}
else if (e[7] == '0') {
_version = HTTP_1_0;
}
else {
_version = HTTP_UNKNOWN;
}
e += versionLength;
}
}
// go on until eol
while (e < end && *e != '\n') {
++e;
}
if (e == end) {
start = end;
}
else {
start = e + 1;
}
}
*valueEnd = '\0';
}
}
// check the key
_type = getRequestType(keyBegin, keyEnd - keyBegin);
// extract the path and decode the url and parameters
if (_type != HTTP_REQUEST_ILLEGAL) {
char* pathBegin = valueBegin;
char* pathEnd = nullptr;
char* paramBegin = nullptr;
char* paramEnd = nullptr;
// find a question mark or space
char* f = pathBegin;
// do NOT url-decode the path, we need to distingush between
// "/document/a/b" and "/document/a%2fb"
while (f < valueEnd && *f != '?' && *f != ' ' && *f != '\n') {
++f;
}
pathEnd = f;
// look for database name in URL
if (pathEnd - pathBegin >= 5) {
char* q = pathBegin;
// check if the prefix is "_db"
if (q[0] == '/' && q[1] == '_' && q[2] == 'd' && q[3] == 'b' && q[4] == '/') {
// request contains database name
q += 5;
pathBegin = q;
// read until end of database name
while (*q != '\0') {
if (*q == '/' || *q == '?' || *q == ' ' || *q == '\n' || *q == '\r') {
break;
}
++q;
}
_databaseName = string(pathBegin, q - pathBegin);
pathBegin = q;
}
}
// no space, question mark or end-of-line
if (f == valueEnd) {
paramEnd = paramBegin = pathEnd;
// set full url = complete path
setFullUrl(pathBegin, pathEnd);
}
// no question mark
else if (*f == ' ' || *f == '\n') {
*pathEnd = '\0';
paramEnd = paramBegin = pathEnd;
// set full url = complete path
setFullUrl(pathBegin, pathEnd);
}
// found a question mark
else {
paramBegin = f + 1;
paramEnd = paramBegin;
while (paramEnd < valueEnd && *paramEnd != ' ' && *paramEnd != '\n') {
++paramEnd;
}
// set full url = complete path + url parameters
setFullUrl(pathBegin, paramEnd);
// now that the full url was saved, we can insert the null bytes
*pathEnd = '\0';
*paramEnd = '\0';
}
if (pathBegin < pathEnd) {
setRequestPath(pathBegin);
}
if (paramBegin < paramEnd) {
setValues(paramBegin, paramEnd);
}
}
}
// no space, either eol or newline
else {
if (e < end) {
*keyEnd = '\0';
start = e + 1;
}
else {
start = end;
}
// check the key
_type = getRequestType(keyBegin, keyEnd - keyBegin);
}
}
// .............................................................................
// OTHER LINES
// .............................................................................
else {
// split line at colon
char* e = lineBegin;
for (; e < end && *e != ':' && *e != '\n'; ++e) {
*e = ::tolower(*e);
}
// store key and value
char* keyBegin = lineBegin;
char* keyEnd = e;
// we found a colon (*end is '\0')
if (*e == ':') {
char* valueBegin = nullptr;
char* valueEnd = nullptr;
// extract the value
keyEnd = e++;
while (e < end && *e == ' ') {
++e;
}
while (keyBegin < keyEnd && keyEnd[-1] == ' ') {
--keyEnd;
}
*keyEnd = '\0';
// there is no value at all
if (e == end) {
valueBegin = valueEnd = keyEnd;
start = end;
}
else if (*e == '\n') {
valueBegin = valueEnd = keyEnd;
start = e + 1;
}
// find \n
else {
valueBegin = e;
while (e < end && *e != '\n') {
++e;
}
if (e == end) {
valueEnd = e;
start = end;
}
else {
valueEnd = e;
start = e + 1;
}
// skip \r
if (valueBegin < valueEnd && valueEnd[-1] == '\r') {
--valueEnd;
}
// skip trailing spaces
while (valueBegin < valueEnd && valueEnd[-1] == ' ') {
--valueEnd;
}
*valueEnd = '\0';
}
if (keyBegin < keyEnd) {
setHeader(keyBegin, keyEnd - keyBegin, valueBegin);
}
}
// we found no colon, either eol or newline. Take the whole line as key
else {
if (e < end) {
*keyEnd = '\0';
start = e + 1;
}
else {
start = end;
}
// skip \r
if (keyBegin < keyEnd && keyEnd[-1] == '\r') {
--keyEnd;
}
// use empty value
if (keyBegin < keyEnd) {
setHeader(keyBegin, keyEnd - keyBegin, keyEnd);
}
}
}
lineNum++;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the full url
/// this will create a copy of the characters in the range, so the original
/// range can be modified afterwards
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setFullUrl (char const* begin, char const* end) {
TRI_ASSERT(begin != nullptr);
TRI_ASSERT(end != nullptr);
TRI_ASSERT(begin <= end);
_fullUrl = string(begin, end - begin);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the header values
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setValues (char* buffer, char* end) {
char* keyBegin = nullptr;
char* key = nullptr;
char* valueBegin = nullptr;
char* value = nullptr;
enum { KEY, VALUE } phase = KEY;
enum { NORMAL, HEX1, HEX2 } reader = NORMAL;
int hex = 0;
char const AMB = '&';
char const EQUAL = '=';
char const PERCENT = '%';
char const PLUS = '+';
for (keyBegin = key = buffer; buffer < end; buffer++) {
char next = *buffer;
if (phase == KEY && next == EQUAL) {
phase = VALUE;
valueBegin = value = buffer + 1;
continue;
}
else if (next == AMB) {
phase = KEY;
*key = '\0';
// check for missing value phase
if (valueBegin == nullptr) {
valueBegin = value = key;
}
else {
*value = '\0';
}
if (key - keyBegin > 2 && (*(key - 2)) == '[' && (*(key - 1)) == ']') {
// found parameter xxx[]
*(key - 2) = '\0';
setArrayValue(keyBegin, key - keyBegin - 2, valueBegin);
}
else {
_values.insert(keyBegin, key - keyBegin, valueBegin);
}
keyBegin = key = buffer + 1;
valueBegin = value = 0;
continue;
}
else if (next == PERCENT) {
reader = HEX1;
continue;
}
else if (reader == HEX1) {
int h1 = StringUtils::hex2int(next, -1);
if (h1 == -1) {
reader = NORMAL;
--buffer;
continue;
}
hex = h1 * 16;
reader = HEX2;
continue;
}
else if (reader == HEX2) {
int h1 = StringUtils::hex2int(next, -1);
if (h1 == -1) {
--buffer;
}
else {
hex += h1;
}
reader = NORMAL;
next = static_cast<char>(hex);
}
else if (next == PLUS) {
next = ' ';
}
if (phase == KEY) {
*key++ = next;
}
else {
*value++ = next;
}
}
if (keyBegin != key) {
*key = '\0';
// check for missing value phase
if (valueBegin == nullptr) {
valueBegin = value = key;
}
else {
*value = '\0';
}
if (key - keyBegin > 2 && (*(key - 2)) == '[' && (*(key - 1)) == ']') {
// found parameter xxx[]
*(key - 2) = '\0';
setArrayValue(keyBegin, key - keyBegin - 2, valueBegin);
}
else {
_values.insert(keyBegin, key - keyBegin, valueBegin);
}
}
}
// -----------------------------------------------------------------------------
// --SECTION-- public prefix/suffix methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief returns the prefix path of the request
////////////////////////////////////////////////////////////////////////////////
char const* HttpRequest::prefix () const {
return _prefix.c_str();
}
////////////////////////////////////////////////////////////////////////////////
/// @brief sets the path of the request
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setPrefix (char const* path) {
_prefix = path;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns all suffix parts
////////////////////////////////////////////////////////////////////////////////
vector<string> const& HttpRequest::suffix () const {
return _suffix;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief adds a suffix part
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::addSuffix (std::string const& part) {
string decoded = StringUtils::urlDecode(part);
size_t tmpLength = 0;
char* utf8_nfc = TRI_normalize_utf8_to_NFC(TRI_UNKNOWN_MEM_ZONE, decoded.c_str(), decoded.length(), &tmpLength);
if (utf8_nfc) {
_suffix.emplace_back(utf8_nfc);
TRI_Free(TRI_UNKNOWN_MEM_ZONE, utf8_nfc);
}
else {
_suffix.emplace_back(decoded);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief set the request context
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setRequestContext (RequestContext* requestContext,
bool isRequestContextOwner) {
if (_requestContext) {
// if we have a shared context, we should not have got here
TRI_ASSERT(isRequestContextOwner);
// delete any previous context
TRI_ASSERT(false);
delete _requestContext;
}
_requestContext = requestContext;
_isRequestContextOwner = isRequestContextOwner;
}
// -----------------------------------------------------------------------------
// --SECTION-- public static methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief translate the HTTP protocol version
////////////////////////////////////////////////////////////////////////////////
std::string HttpRequest::translateVersion (HttpVersion version) {
switch (version) {
case HTTP_1_1: {
return "HTTP/1.1";
}
case HTTP_1_0:
case HTTP_UNKNOWN:
default: {
return "HTTP/1.0";
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief translate an enum value into an HTTP method string
////////////////////////////////////////////////////////////////////////////////
string HttpRequest::translateMethod (const HttpRequestType method) {
if (method == HTTP_REQUEST_DELETE) {
return "DELETE";
}
else if (method == HTTP_REQUEST_GET) {
return "GET";
}
else if (method == HTTP_REQUEST_HEAD) {
return "HEAD";
}
else if (method == HTTP_REQUEST_OPTIONS) {
return "OPTIONS";
}
else if (method == HTTP_REQUEST_PATCH) {
return "PATCH";
}
else if (method == HTTP_REQUEST_POST) {
return "POST";
}
else if (method == HTTP_REQUEST_PUT) {
return "PUT";
}
LOG_WARNING("illegal http request method encountered in switch");
return "UNKNOWN";
}
////////////////////////////////////////////////////////////////////////////////
/// @brief translate an HTTP method string into an enum value
////////////////////////////////////////////////////////////////////////////////
HttpRequest::HttpRequestType HttpRequest::translateMethod (const string& method) {
const string methodString = StringUtils::toupper(method);
if (methodString == "DELETE") {
return HTTP_REQUEST_DELETE;
}
else if (methodString == "GET") {
return HTTP_REQUEST_GET;
}
else if (methodString == "HEAD") {
return HTTP_REQUEST_HEAD;
}
else if (methodString == "OPTIONS") {
return HTTP_REQUEST_OPTIONS;
}
else if (methodString == "PATCH") {
return HTTP_REQUEST_PATCH;
}
else if (methodString == "POST") {
return HTTP_REQUEST_POST;
}
else if (methodString == "PUT") {
return HTTP_REQUEST_PUT;
}
return HTTP_REQUEST_ILLEGAL;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief append the request method string to a string buffer
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::appendMethod (HttpRequestType method, StringBuffer* buffer) {
buffer->appendText(translateMethod(method));
buffer->appendChar(' ');
}
////////////////////////////////////////////////////////////////////////////////
/// @brief set array value
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setArrayValue (char* key, size_t length, char const* value) {
Dictionary< vector<char const*>* >::KeyValue const* kv = _arrayValues.lookup(key);
vector<char const*>* v = nullptr;
if (kv == nullptr) {
v = new vector<char const*>;
_arrayValues.insert(key, length, v);
}
else {
v = kv->_value;
}
v->push_back(value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief set cookie
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::setCookie (char* key, size_t length, char const* value) {
_cookies.insert(key, length, value);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief parse value of a cookie header field
////////////////////////////////////////////////////////////////////////////////
void HttpRequest::parseCookies (const char* buffer) {
char* keyBegin = nullptr;
char* key = nullptr;
char* valueBegin = nullptr;
char* value = nullptr;
enum { KEY, VALUE } phase = KEY;
enum { NORMAL, HEX1, HEX2 } reader = NORMAL;
int hex = 0;
char const AMB = ';';
char const EQUAL = '=';
char const PERCENT = '%';
char const SPACE = ' ';
char* buffer2 = (char*) buffer;
char* end = buffer2 + strlen(buffer);
for (keyBegin = key = buffer2; buffer2 < end; buffer2++) {
char next = *buffer2;
if (phase == KEY && next == EQUAL) {
phase = VALUE;
valueBegin = value = buffer2 + 1;
continue;
}
else if (next == AMB) {
phase = KEY;
*key = '\0';
// check for missing value phase
if (valueBegin == nullptr) {
valueBegin = value = key;
}
else {
*value = '\0';
}
setCookie(keyBegin, key - keyBegin, valueBegin);
//keyBegin = key = buffer2 + 1;
while ( *(keyBegin = key = buffer2 + 1) == SPACE && buffer2 < end) {
buffer2++;
}
valueBegin = value = nullptr;
continue;
}
else if (next == PERCENT) {
reader = HEX1;
continue;
}
else if (reader == HEX1) {
int h1 = StringUtils::hex2int(next, -1);
if (h1 == -1) {
reader = NORMAL;
--buffer2;
continue;
}
hex = h1 * 16;
reader = HEX2;
continue;
}
else if (reader == HEX2) {
int h1 = StringUtils::hex2int(next, -1);
if (h1 == -1) {
--buffer2;
}
else {
hex += h1;
}
reader = NORMAL;
next = static_cast<char>(hex);
}
if (phase == KEY) {
*key++ = next;
}
else {
*value++ = next;
}
}
if (keyBegin != key) {
*key = '\0';
// check for missing value phase
if (valueBegin == nullptr) {
valueBegin = value = key;
}
else {
*value = '\0';
}
setCookie(keyBegin, key - keyBegin, valueBegin);
}
}
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}"
// End: