Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rest] add query parsing #1940

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 82 additions & 45 deletions src/rest/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,105 +34,142 @@
namespace otbr {
namespace rest {

static int OnUrl(http_parser *parser, const char *at, size_t len)
Parser::Parser(Request *aRequest)
{
mState.mRequest = aRequest;

mParser.data = &mState;
}

void Parser::Init(void)
{
mSettings.on_message_begin = Parser::OnMessageBegin;
mSettings.on_url = Parser::OnUrl;
mSettings.on_status = Parser::OnHandlerData;
mSettings.on_header_field = Parser::OnHeaderField;
mSettings.on_header_value = Parser::OnHeaderData;
mSettings.on_body = Parser::OnBody;
mSettings.on_headers_complete = Parser::OnHeaderComplete;
mSettings.on_message_complete = Parser::OnMessageComplete;
http_parser_init(&mParser, HTTP_REQUEST);
}

void Parser::Process(const char *aBuf, size_t aLength)
{
Request *request = reinterpret_cast<Request *>(parser->data);
http_parser_execute(&mParser, &mSettings, aBuf, aLength);
}

int Parser::OnUrl(http_parser *parser, const char *at, size_t len)
{
State *state = reinterpret_cast<State *>(parser->data);

if (len > 0)
{
request->SetUrl(at, len);
state->mUrl.append(at, len);
}

return 0;
}

static int OnBody(http_parser *parser, const char *at, size_t len)
int Parser::OnBody(http_parser *parser, const char *at, size_t len)
{
Request *request = reinterpret_cast<Request *>(parser->data);
State *state = reinterpret_cast<State *>(parser->data);

if (len > 0)
{
request->SetBody(at, len);
state->mRequest->SetBody(at, len);
}

return 0;
}

static int OnMessageComplete(http_parser *parser)
int Parser::OnMessageComplete(http_parser *parser)
{
Request *request = reinterpret_cast<Request *>(parser->data);
State *state = reinterpret_cast<State *>(parser->data);

http_parser_url urlParser;
http_parser_url_init(&urlParser);
http_parser_parse_url(state->mUrl.c_str(), state->mUrl.length(), 1, &urlParser);

if (urlParser.field_set & (1 << UF_PATH))
{
std::string path = state->mUrl.substr(urlParser.field_data[UF_PATH].off, urlParser.field_data[UF_PATH].len);
state->mRequest->SetUrlPath(path);
}

request->SetReadComplete();
if (urlParser.field_set & (1 << UF_QUERY))
{
uint16_t offset = urlParser.field_data[UF_QUERY].off;
uint16_t end = offset + urlParser.field_data[UF_QUERY].len;

while(offset < end) {
std::string::size_type next = state->mUrl.find('&', offset);
if (next == std::string::npos)
{
next = end;
}

std::string::size_type split = state->mUrl.find('=', offset);
if (split != std::string::npos && static_cast<uint16_t>(split) < next)
{
std::string query = state->mUrl.substr(offset, split - offset);
std::string value = state->mUrl.substr(split + 1, next - split - 1);

state->mRequest->AddQueryField(query, value);
}

offset = static_cast<uint16_t>(next + 1);
}
}

state->mRequest->SetReadComplete();

return 0;
}

static int OnMessageBegin(http_parser *parser)
int Parser::OnMessageBegin(http_parser *parser)
{
Request *request = reinterpret_cast<Request *>(parser->data);
request->ResetReadComplete();
State *state = reinterpret_cast<State *>(parser->data);
state->mRequest->ResetReadComplete();

return 0;
}

static int OnHeaderComplete(http_parser *parser)
int Parser::OnHeaderComplete(http_parser *parser)
{
Request *request = reinterpret_cast<Request *>(parser->data);
request->SetMethod(parser->method);
State *state = reinterpret_cast<State *>(parser->data);
state->mRequest->SetMethod(parser->method);
return 0;
}

static int OnHandlerData(http_parser *, const char *, size_t)
int Parser::OnHandlerData(http_parser *, const char *, size_t)
{
return 0;
}

static int OnHeaderField(http_parser *parser, const char *at, size_t len)
int Parser::OnHeaderField(http_parser *parser, const char *at, size_t len)
{
Request *request = reinterpret_cast<Request *>(parser->data);
State *state = reinterpret_cast<State *>(parser->data);

if (len > 0)
{
request->SetNextHeaderField(at, len);
state->mNextHeaderField = std::string(at, len);
}

return 0;
}

static int OnHeaderData(http_parser *parser, const char *at, size_t len)
int Parser::OnHeaderData(http_parser *parser, const char *at, size_t len)
{
Request *request = reinterpret_cast<Request *>(parser->data);
State *state = reinterpret_cast<State *>(parser->data);

if (len > 0)
{
request->SetHeaderValue(at, len);
state->mRequest->AddHeaderField(state->mNextHeaderField, std::string(at, len));
}

return 0;
}

Parser::Parser(Request *aRequest)
{
mParser.data = aRequest;
}

void Parser::Init(void)
{
mSettings.on_message_begin = OnMessageBegin;
mSettings.on_url = OnUrl;
mSettings.on_status = OnHandlerData;
mSettings.on_header_field = OnHeaderField;
mSettings.on_header_value = OnHeaderData;
mSettings.on_body = OnBody;
mSettings.on_headers_complete = OnHeaderComplete;
mSettings.on_message_complete = OnMessageComplete;
http_parser_init(&mParser, HTTP_REQUEST);
}

void Parser::Process(const char *aBuf, size_t aLength)
{
http_parser_execute(&mParser, &mSettings, aBuf, aLength);
}

} // namespace rest
} // namespace otbr
17 changes: 17 additions & 0 deletions src/rest/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,25 @@ class Parser
void Process(const char *aBuf, size_t aLength);

private:
class State {
public:
CodingRays marked this conversation as resolved.
Show resolved Hide resolved
Request *mRequest;
std::string mUrl;
std::string mNextHeaderField;
};

static int OnUrl(http_parser *parser, const char *at, size_t len);
static int OnBody(http_parser *parser, const char *at, size_t len);
static int OnMessageComplete(http_parser *parser);
static int OnMessageBegin(http_parser *parser);
static int OnHeaderComplete(http_parser *parser);
static int OnHandlerData(http_parser *, const char *, size_t);
static int OnHeaderField(http_parser *parser, const char *at, size_t len);
static int OnHeaderData(http_parser *parser, const char *at, size_t len);

http_parser mParser;
http_parser_settings mSettings;
State mState;
};

} // namespace rest
Expand Down
39 changes: 15 additions & 24 deletions src/rest/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ Request::Request(void)
{
}

void Request::SetUrl(const char *aString, size_t aLength)
void Request::SetUrlPath(std::string aPath)
{
mUrl += std::string(aString, aLength);
mUrlPath = aPath;
}

void Request::SetBody(const char *aString, size_t aLength)
Expand All @@ -57,14 +57,14 @@ void Request::SetMethod(int32_t aMethod)
mMethod = aMethod;
}

void Request::SetNextHeaderField(const char *aString, size_t aLength)
void Request::AddHeaderField(std::string aField, std::string aValue)
{
mNextHeaderField = StringUtils::ToLowercase(std::string(aString, aLength));
mHeaders[aField] = aValue;
}

void Request::SetHeaderValue(const char *aString, size_t aLength)
void Request::AddQueryField(std::string aField, std::string aValue)
{
mHeaders[mNextHeaderField] = std::string(aString, aLength);
mQueryParameters[aField] = aValue;
}

HttpMethod Request::GetMethod() const
Expand All @@ -77,25 +77,9 @@ std::string Request::GetBody() const
return mBody;
}

std::string Request::GetUrl(void) const
std::string Request::GetUrlPath(void) const
{
std::string url = mUrl;

size_t urlEnd = url.find("?");

if (urlEnd != std::string::npos)
{
url = url.substr(0, urlEnd);
}
while (!url.empty() && url[url.size() - 1] == '/')
{
url.pop_back();
}

VerifyOrExit(url.size() > 0, url = "/");

exit:
return url;
return mUrlPath;
}

std::string Request::GetHeaderValue(const std::string aHeaderField) const
Expand All @@ -105,6 +89,13 @@ std::string Request::GetHeaderValue(const std::string aHeaderField) const
return (it == mHeaders.end()) ? "" : it->second;
}

std::string Request::GetQueryValue(const std::string aQueryName) const
{
auto it = mHeaders.find(aQueryName);

return (it == mHeaders.end()) ? "" : it->second;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we use mQueryParameters here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we definitely should.
I've tested the parsing code but not the api. Looking at the automated tests its gonna be hard to test without having a endpoint that actually uses the query parameters. We have code under works that uses them but only for timeouts so that would not be testable either.

}

void Request::SetReadComplete(void)
{
mComplete = true;
Expand Down
36 changes: 21 additions & 15 deletions src/rest/request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,12 @@ class Request
Request(void);

/**
* This method sets the Url field of a request.
* This method sets the Url Path field of a request.
*
* @param[in] aString A pointer points to url string.
* @param[in] aLength Length of the url string
* @param[in] aPath The url path
*
*/
void SetUrl(const char *aString, size_t aLength);
void SetUrlPath(std::string aPath);

/**
* This method sets the body field of a request.
Expand Down Expand Up @@ -95,20 +94,19 @@ class Request
/**
* This method sets the next header field of a request.
*
* @param[in] aString A pointer points to body string.
* @param[in] aLength Length of the body string
* @param[in] aField The field name.
* @param[in] aValue The value of the field.
*
*/
void SetNextHeaderField(const char *aString, size_t aLength);
void AddHeaderField(std::string aField, std::string aValue);

/**
* This method sets the header value of the previously set header of a request.
*
* @param[in] aString A pointer points to body string.
* @param[in] aLength Length of the body string
* This method adds a query field to the request.
*
* @param[in] aField The field name.
* @param[in] aValue The value of the field.
*/
void SetHeaderValue(const char *aString, size_t aLength);
void AddQueryField(std::string aField, std::string aValue);

/**
* This method labels the request as complete which means it no longer need to be parsed one more time .
Expand Down Expand Up @@ -141,7 +139,7 @@ class Request
*
* @returns A string contains the url of this request.
*/
std::string GetUrl(void) const;
std::string GetUrlPath(void) const;

/**
* This method returns the specified header field for this request.
Expand All @@ -151,6 +149,14 @@ class Request
*/
std::string GetHeaderValue(const std::string aHeaderField) const;

/**
* This method returns the specified query for this request.
*
* @param aQueryName A query name.
* @return A string containing the value of the query or an empty string if the query could not be found.
*/
std::string GetQueryValue(const std::string aQueryName) const;

/**
* This method indicates whether this request is parsed completely.
*
Expand All @@ -161,10 +167,10 @@ class Request
private:
int32_t mMethod;
size_t mContentLength;
std::string mUrl;
std::string mUrlPath;
std::string mBody;
std::string mNextHeaderField;
std::map<std::string, std::string> mHeaders;
std::map<std::string, std::string> mQueryParameters;
bool mComplete;
};

Expand Down
4 changes: 2 additions & 2 deletions src/rest/resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void Resource::Init(void)

void Resource::Handle(Request &aRequest, Response &aResponse) const
{
std::string url = aRequest.GetUrl();
std::string url = aRequest.GetUrlPath();
auto it = mResourceMap.find(url);

if (it != mResourceMap.end())
Expand All @@ -167,7 +167,7 @@ void Resource::Handle(Request &aRequest, Response &aResponse) const

void Resource::HandleCallback(Request &aRequest, Response &aResponse)
{
std::string url = aRequest.GetUrl();
std::string url = aRequest.GetUrlPath();
auto it = mResourceCallbackMap.find(url);

if (it != mResourceCallbackMap.end())
Expand Down