Commit 4035c5f1 authored by octal's avatar octal

Implemented cookies parsing

parent fa382599
......@@ -2,6 +2,7 @@
#include "peer.h"
#include "http.h"
#include "http_headers.h"
#include "cookie.h"
#include "router.h"
#include <iostream>
#include <cstring>
......@@ -20,6 +21,16 @@ struct ExceptionPrinter {
}
};
void printCookies(const Net::Http::Request& req) {
auto cookies = req.cookies();
std::cout << "Cookies: [" << std::endl;
const std::string indent(4, ' ');
for (const auto& c: cookies) {
std::cout << indent << c.name << " = " << c.value << std::endl;
}
std::cout << "]" << std::endl;
}
class MyHandler : public Net::Http::Handler {
void onRequest(
const Net::Http::Request& req,
......@@ -41,6 +52,9 @@ class MyHandler : public Net::Http::Handler {
.add<Header::Server>("lys")
.add<Header::ContentType>(MIME(Text, Plain));
response.cookies()
.add(Cookie("lang", "en-US"));
auto stream = response.stream(Net::Http::Code::Ok);
stream << "PO";
stream << "NG";
......@@ -181,6 +195,7 @@ private:
Routes::Post(router, "/record/:name/:value?", Routes::bind(&StatsEndpoint::doRecordMetric, this));
Routes::Get(router, "/value/:name", Routes::bind(&StatsEndpoint::doGetMetric, this));
Routes::Get(router, "/ready", Routes::bind(&Generic::handleReady));
Routes::Get(router, "/auth", Routes::bind(&StatsEndpoint::doAuth, this));
}
......@@ -223,6 +238,13 @@ private:
}
void doAuth(const Rest::Request& request, Net::Http::Response response) {
printCookies(request);
response.cookies()
.add(Http::Cookie("lang", "en-US"));
response.send(Http::Code::Ok);
}
class Metric {
public:
Metric(std::string name, int initialValue = 1)
......
......@@ -11,6 +11,7 @@ set(SOURCE_FILES
http_headers.cc
http_defs.cc
mime.cc
cookie.cc
stream.cc
router.cc
)
......
/*
Mathieu Stefani, 16 janvier 2016
Cookie implementation
*/
#include "cookie.h"
#include "stream.h"
#include <iterator>
#include <unordered_map>
namespace Net {
namespace Http {
namespace {
StreamCursor::Token matchValue(StreamCursor& cursor) {
int c;
if ((c = cursor.current()) != StreamCursor::Eof && c != '=')
throw std::runtime_error("Invalid cookie");
if (!cursor.advance(1))
throw std::runtime_error("Invalid cookie, early eof");
StreamCursor::Token token(cursor);
match_until(';', cursor);
return token;
}
template<typename T>
struct AttributeMatcher;
template<>
struct AttributeMatcher<Optional<std::string>> {
static void match(StreamCursor& cursor, Cookie* obj, Optional<std::string> Cookie::*attr) {
auto token = matchValue(cursor);
obj->*attr = Some(std::string(token.rawText(), token.size()));
}
};
template<>
struct AttributeMatcher<Optional<int>> {
static void match(StreamCursor& cursor, Cookie* obj, Optional<int> Cookie::*attr) {
auto token = matchValue(cursor);
auto strntol = [](const char *str, size_t len) {
int ret = 0;
for (size_t i = 0; i < len; ++i) {
if (!isdigit(str[i]))
throw std::invalid_argument("Invalid conversion");
ret *= 10;
ret += str[i] - '0';
};
return ret;
};
obj->*attr = Some(strntol(token.rawText(), token.size()));
}
};
template<>
struct AttributeMatcher<bool> {
static void match(StreamCursor& cursor, Cookie* obj, bool Cookie::*attr) {
obj->*attr = true;
}
};
template<>
struct AttributeMatcher<Optional<FullDate>> {
static void match(StreamCursor& cursor, Cookie* obj, Optional<FullDate> Cookie::*attr) {
auto token = matchValue(cursor);
obj->*attr = Some(FullDate::fromRaw(token.rawText(), token.size()));
}
};
template<typename T>
bool match_attribute(const char* name, size_t len, StreamCursor& cursor, Cookie* obj, T Cookie::*attr) {
if (match_string(name, len, cursor)) {
AttributeMatcher<T>::match(cursor, obj, attr);
cursor.advance(1);
return true;
}
return false;
}
}
Cookie::Cookie(std::string name, std::string value)
: name(std::move(name))
, value(std::move(value))
, secure(false)
, httpOnly(false)
{
}
Cookie
Cookie::fromRaw(const char* str, size_t len)
{
RawStreamBuf<> buf(const_cast<char *>(str), len);
StreamCursor cursor(&buf);
StreamCursor::Token nameToken(cursor);
if (!match_until('=', cursor))
throw std::runtime_error("Invalid cookie, missing value");
auto name = nameToken.text();
if (!cursor.advance(1))
throw std::runtime_error("Invalid cookie, missing value");
StreamCursor::Token valueToken(cursor);
match_until(';', cursor);
auto value = valueToken.text();
Cookie cookie(std::move(name), std::move(value));
if (cursor.eof()) {
return cookie;
}
cursor.advance(1);
#define STR(str) str, sizeof(str) - 1
int c;
do {
skip_whitespaces(cursor);
if (match_attribute(STR("Path"), cursor, &cookie, &Cookie::path)) ;
else if (match_attribute(STR("Domain"), cursor, &cookie, &Cookie::domain)) ;
else if (match_attribute(STR("Secure"), cursor, &cookie, &Cookie::secure)) ;
else if (match_attribute(STR("HttpOnly"), cursor, &cookie, &Cookie::httpOnly)) ;
else if (match_attribute(STR("Max-Age"), cursor, &cookie, &Cookie::maxAge)) ;
else if (match_attribute(STR("Expires"), cursor, &cookie, &Cookie::expires)) ;
// ext
else {
StreamCursor::Token nameToken(cursor);
match_until('=', cursor);
auto name = nameToken.text();
std::string value;
if (!cursor.eof()) {
auto token = matchValue(cursor);
value = token.text();
}
cookie.ext.insert(std::make_pair(std::move(name), std::move(value)));
}
} while (!cursor.eof());
#undef STR
return cookie;
}
Cookie
Cookie::fromString(const std::string& str) {
return Cookie::fromRaw(str.c_str(), str.size());
}
void
Cookie::write(std::ostream& os) const {
os << name << "=" << value;
optionally_do(path, [&](const std::string& value) {
os << "; ";
os << "Path=" << value;
});
optionally_do(domain, [&](const std::string& value) {
os << "; ";
os << "Domain=" << value;
});
optionally_do(maxAge, [&](int value) {
os << "; ";
os << "Max-Age=" << value;
});
optionally_do(expires, [&](const FullDate& value) {
os << "; ";
os << "Expires=";
value.write(os);
});
if (secure)
os << "; Secure";
if (httpOnly)
os << "; HttpOnly";
if (!ext.empty()) {
os << "; ";
for (auto it = std::begin(ext), end = std::end(ext); it != end; ++it) {
os << it->first << "=" << it->second;
if (std::distance(it, end) > 1)
os << "; ";
}
}
}
CookieJar::CookieJar()
{
}
void
CookieJar::add(const Cookie& cookie) {
cookies.insert(std::make_pair(cookie.name, cookie));
}
Cookie
CookieJar::get(const std::string& name) const {
auto it = cookies.find(name);
if (it == std::end(cookies))
throw std::runtime_error("Could not find requested cookie");
return it->second;
}
bool
CookieJar::has(const std::string& name) const {
auto it = cookies.find(name);
return it != std::end(cookies);
}
} // namespace Http
} // namespace Net
/*
Mathieu Stefani, 16 janvier 2016
Representation of a Cookie as per http://tools.ietf.org/html/rfc6265
*/
#pragma once
#include "optional.h"
#include "http_defs.h"
#include <ctime>
#include <string>
#include <map>
#include <unordered_map>
namespace Net {
namespace Http {
struct Cookie {
Cookie(std::string name, std::string value);
std::string name;
std::string value;
Optional<std::string> path;
Optional<std::string> domain;
Optional<FullDate> expires;
Optional<int> maxAge;
bool secure;
bool httpOnly;
std::map<std::string, std::string> ext;
static Cookie fromRaw(const char* str, size_t len);
static Cookie fromString(const std::string& str);
void write(std::ostream& os) const;
};
class CookieJar {
public:
typedef std::unordered_map<std::string, Cookie> Storage;
struct iterator : std::iterator<std::bidirectional_iterator_tag, Cookie> {
iterator(const Storage::const_iterator& iterator)
: it_(iterator)
{ }
Cookie operator*() const {
return it_->second;
}
iterator operator++() {
++it_;
return iterator(it_);
}
iterator operator++(int) {
iterator ret(it_);
it_++;
return ret;
}
bool operator !=(iterator other) const {
return it_ != other.it_;
}
bool operator==(iterator other) const {
return it_ == other.it_;
}
private:
Storage::const_iterator it_;
};
CookieJar();
void add(const Cookie& cookie);
Cookie get(const std::string& name) const;
bool has(const std::string& name) const;
iterator begin() const {
return iterator(cookies.begin());
}
iterator end() const {
return iterator(cookies.end());
}
private:
Storage cookies;
};
} // namespace Net
} // namespace Http
......@@ -64,7 +64,7 @@ namespace {
#define OUT(...) \
do { \
__VA_ARGS__; \
if (!os) return false; \
if (!os) return false; \
} while (0)
std::ostream os(&buf);
......@@ -80,6 +80,25 @@ namespace {
#undef OUT
}
bool writeCookies(const CookieJar& cookies, DynamicStreamBuf& buf) {
#define OUT(...) \
do { \
__VA_ARGS__; \
if (!os) return false; \
} while (0)
std::ostream os(&buf);
for (const auto& cookie: cookies) {
OUT(os << "Set-Cookie: ");
OUT(cookie.write(os));
OUT(os << crlf);
}
return true;
#undef OUT
}
}
namespace Private {
......@@ -228,7 +247,13 @@ namespace Private {
if (!cursor.advance(1)) return State::Again;
}
if (Header::Registry::isRegistered(name)) {
if (name == "Cookie") {
request->cookies_.add(
Cookie::fromRaw(cursor.offset(start), cursor.diff(start))
);
}
else if (Header::Registry::isRegistered(name)) {
std::shared_ptr<Header::Header> header = Header::Registry::makeHeader(name);
header->parseRaw(cursor.offset(start), cursor.diff(start));
request->headers_.add(header);
......@@ -404,6 +429,11 @@ Request::query() const {
return query_;
}
const CookieJar&
Request::cookies() const {
return cookies_;
}
#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME
std::shared_ptr<Tcp::Peer>
Request::peer() const {
......@@ -428,15 +458,16 @@ ResponseStream::ResponseStream(
if (!writeStatusLine(code_, buf_))
throw Error("Response exceeded buffer size");
if (!writeCookies(cookies_, buf_)) {
throw Error("Response exceeded buffer size");
}
if (writeHeaders(headers_, buf_)) {
std::ostream os(&buf_);
if (!writeHeader<Header::TransferEncoding>(os, Header::Encoding::Chunked))
throw Error("Response exceeded buffer size");
os << crlf;
}
else {
throw Error("Response exceeded buffer size");
}
}
void
......@@ -479,6 +510,7 @@ Response::putOnWire(const char* data, size_t len)
OUT(writeStatusLine(code_, buf_));
OUT(writeHeaders(headers_, buf_));
OUT(writeCookies(cookies_, buf_));
OUT(writeHeader<Header::ContentLength>(os, len));
......
......@@ -14,6 +14,7 @@
#include "net.h"
#include "http_headers.h"
#include "http_defs.h"
#include "cookie.h"
#include "stream.h"
#include "mime.h"
#include "async.h"
......@@ -53,6 +54,8 @@ public:
Header::Collection headers_;
std::string body_;
CookieJar cookies_;
};
namespace Uri {
......@@ -91,6 +94,8 @@ public:
const Header::Collection& headers() const;
const Uri::Query& query() const;
const CookieJar& cookies() const;
/* @Investigate: this is disabled because of a lock in the shared_ptr / weak_ptr
implementation of libstdc++. Under contention, we experience a performance
drop of 5x with that lock
......@@ -208,6 +213,10 @@ public:
return headers_;
}
const CookieJar& cookies() const {
return cookies_;
}
Code code() const {
return code_;
}
......@@ -295,6 +304,14 @@ public:
return headers_;
}
const CookieJar& cookies() const {
return cookies_;
}
CookieJar& cookies() {
return cookies_;
}
Code code() const {
return code_;
}
......
......@@ -7,6 +7,7 @@
#include "http_defs.h"
#include "common.h"
#include <iostream>
#include <iomanip>
namespace Net {
......@@ -107,6 +108,24 @@ FullDate::fromString(const std::string& str) {
return FullDate::fromRaw(str.c_str(), str.size());
}
void
FullDate::write(std::ostream& os, Type type) const
{
char buff[100];
std::memset(buff, 0, sizeof buff);
switch (type) {
case Type::RFC1123:
//os << std::put_time(&date_, "%a, %d %b %Y %H:%M:%S %Z");
if (std::strftime(buff, sizeof buff, "%a, %d %b %Y %H:%M:%S %Z", &date_))
os << buff;
break;
case Type::RFC850:
case Type::AscTime:
break;
}
}
const char* methodString(Method method)
{
switch (method) {
......
......@@ -147,11 +147,18 @@ class FullDate {
public:
FullDate();
enum class Type {
RFC1123,
RFC850,
AscTime
};
FullDate(std::tm date)
: date_(date)
{ }
std::tm date() const { return date_; }
void write(std::ostream& os, Type type = Type::RFC1123) const;
static FullDate fromRaw(const char* str, size_t len);
static FullDate fromString(const std::string& str);
......
......@@ -118,7 +118,7 @@ public:
template<typename U>
Optional(types::Some<U> some) {
static_assert(std::is_same<T, U>::value || std::is_convertible<T, U>::value,
static_assert(std::is_same<T, U>::value || std::is_convertible<U, T>::value,
"Types mismatch");
from_some_helper(std::move(some), types::is_move_constructible<U>());
}
......@@ -126,7 +126,7 @@ public:
template<typename U>
Optional<T> &operator=(types::Some<U> some) {
static_assert(std::is_same<T, U>::value || std::is_convertible<T, U>::value,
static_assert(std::is_same<T, U>::value || std::is_convertible<U, T>::value,
"Types mismatch");
if (none_flag != NoneMarker) {
data()->~T();
......
......@@ -143,13 +143,38 @@ match_raw(const void* buf, size_t len, StreamCursor& cursor) {
return false;
}
bool
match_string(const char* str, size_t len, StreamCursor& cursor, CaseSensitivity cs) {
if (cursor.remaining() < len)
return false;
if (cs == CaseSensitivity::Sensitive) {
if (strncmp(cursor.offset(), str, len) == 0) {
cursor.advance(len);
return true;
}
} else {
const char *off = cursor.offset();
for (size_t i = 0; i < len; ++i) {
const char lhs = std::tolower(str[i]);
const char rhs = std::tolower(off[i]);
if (lhs != rhs) return false;
}
cursor.advance(len);
return true;
}
return false;
}
bool
match_literal(char c, StreamCursor& cursor, CaseSensitivity cs) {
if (cursor.eof())
return false;
char lhs = (cs == CaseSensitivity::Sensitive ? c : std::tolower(c));
char rhs = (cs == CaseSensitivity::Insensitive ? cursor.current() : std::tolower(cursor.current()));
char rhs = (cs == CaseSensitivity::Sensitive ? cursor.current() : std::tolower(cursor.current()));
if (lhs == rhs) {
cursor.advance(1);
......@@ -200,3 +225,14 @@ match_double(double* val, StreamCursor &cursor) {
cursor.advance(static_cast<ptrdiff_t>(end - cursor.offset()));
return true;
}
void
skip_whitespaces(StreamCursor& cursor) {
if (cursor.eof())
return;
int c;
while ((c = cursor.current()) != StreamCursor::Eof && (c == ' ' || c == '\t')) {
cursor.advance(1);
}
}
......@@ -316,7 +316,11 @@ enum class CaseSensitivity {
};
bool match_raw(const void* buf, size_t len, StreamCursor& cursor);
bool match_string(const char *str, size_t len, StreamCursor& cursor,
CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_literal(char c, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_until(char c, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_until(std::initializer_list<char> chars, StreamCursor& cursor, CaseSensitivity cs = CaseSensitivity::Insensitive);
bool match_double(double* val, StreamCursor& cursor);
void skip_whitespaces(StreamCursor& cursor);
......@@ -17,3 +17,7 @@ add_test( typeid_test run_typeid_test )
add_executable( run_router_test router_test.cc )
target_link_libraries(run_router_test gtest gtest_main net)
add_test( router_test run_router_test )
add_executable( run_cookie_test cookie_test.cc )
target_link_libraries(run_cookie_test gtest gtest_main net)
add_test( cookie_test run_cookie_test )
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment