# HG changeset patch # User Sebastien Jodogne # Date 1357728073 -3600 # Node ID 78a8eaa5f30b2f9fdb7881efd78d07150570243b # Parent f579d50fdf8ff377515b26ac4ad45fa4aee3b051 cookies diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/FilesystemHttpHandler.cpp --- a/Core/HttpServer/FilesystemHttpHandler.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/FilesystemHttpHandler.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -54,10 +54,7 @@ { namespace fs = boost::filesystem; - HttpHandler::Arguments header; - header["Content-Type"] = "text/html"; - output.SendOkHeader(header); - + output.SendOkHeader("text/html", false, 0, NULL); output.SendString(""); output.SendString(" "); output.SendString("

Subdirectories

"); diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/HttpFileSender.cpp --- a/Core/HttpServer/HttpFileSender.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/HttpFileSender.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -38,20 +38,7 @@ { void HttpFileSender::SendHeader(HttpOutput& output) { - HttpHandler::Arguments header; - header["Content-Length"] = boost::lexical_cast(GetFileSize()); - - if (contentType_.size() > 0) - { - header["Content-Type"] = contentType_; - } - - if (downloadFilename_.size() > 0) - { - header["Content-Disposition"] = "attachment; filename=\"" + downloadFilename_ + "\""; - } - - output.SendOkHeader(header); + output.SendOkHeader(contentType_.c_str(), true, GetFileSize(), downloadFilename_.c_str()); } void HttpFileSender::Send(HttpOutput& output) diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/HttpHandler.cpp --- a/Core/HttpServer/HttpHandler.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/HttpHandler.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -90,4 +90,44 @@ return it->second; } } + + + + void HttpHandler::ParseCookies(HttpHandler::Arguments& result, + const HttpHandler::Arguments& httpHeaders) + { + result.clear(); + + HttpHandler::Arguments::const_iterator it = httpHeaders.find("cookies"); + if (it != httpHeaders.end()) + { + const std::string& cookies = it->second; + + size_t pos = 0; + while (pos != std::string::npos) + { + size_t nextSemicolon = cookies.find(";", pos); + std::string cookie; + + if (nextSemicolon == std::string::npos) + { + cookie = cookies.substr(pos); + pos = std::string::npos; + } + else + { + cookie = cookies.substr(pos, nextSemicolon - pos); + pos = nextSemicolon + 1; + } + + size_t equal = cookie.find("="); + if (equal != std::string::npos) + { + std::string name = Toolbox::StripSpaces(cookie.substr(0, equal)); + std::string value = Toolbox::StripSpaces(cookie.substr(equal + 1)); + result[name] = value; + } + } + } + } } diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/HttpHandler.h --- a/Core/HttpServer/HttpHandler.h Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/HttpHandler.h Wed Jan 09 11:41:13 2013 +0100 @@ -65,5 +65,8 @@ static std::string GetArgument(const Arguments& getArguments, const std::string& name, const std::string& defaultValue); + + static void ParseCookies(HttpHandler::Arguments& result, + const HttpHandler::Arguments& httpHeaders); }; } diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/HttpOutput.cpp --- a/Core/HttpServer/HttpOutput.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/HttpOutput.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -50,38 +50,46 @@ } } + void HttpOutput::PrepareOkHeader(Header& header, + const char* contentType, + bool hasContentLength, + uint64_t contentLength, + const char* contentFilename) + { + header.clear(); + + if (contentType && contentType[0] != '\0') + { + header.push_back(std::make_pair("Content-Type", std::string(contentType))); + } + + if (hasContentLength) + { + header.push_back(std::make_pair("Content-Length", boost::lexical_cast(contentLength))); + } + + if (contentFilename && contentFilename[0] != '\0') + { + std::string attachment = "attachment; filename=\"" + std::string(contentFilename) + "\""; + header.push_back(std::make_pair("Content-Disposition", attachment)); + } + } + void HttpOutput::SendOkHeader(const char* contentType, bool hasContentLength, uint64_t contentLength, const char* contentFilename) { - std::string s = "HTTP/1.1 200 OK\r\n"; - - if (contentType && contentType[0] != '\0') - { - s += "Content-Type: " + std::string(contentType) + "\r\n"; - } - - if (hasContentLength) - { - s += "Content-Length: " + boost::lexical_cast(contentLength) + "\r\n"; - } - - if (contentFilename && contentFilename[0] != '\0') - { - s += "Content-Disposition: attachment; filename=\"" + std::string(contentFilename) + "\"\r\n"; - } - - s += "\r\n"; - - Send(&s[0], s.size()); + Header header; + PrepareOkHeader(header, contentType, hasContentLength, contentLength, contentFilename); + SendOkHeader(header); } - void HttpOutput::SendOkHeader(const HttpHandler::Arguments& header) + void HttpOutput::SendOkHeader(const Header& header) { std::string s = "HTTP/1.1 200 OK\r\n"; - for (HttpHandler::Arguments::const_iterator + for (Header::const_iterator it = header.begin(); it != header.end(); it++) { s += it->first + ": " + it->second + "\r\n"; @@ -133,6 +141,24 @@ } + void HttpOutput::AnswerBufferWithContentType(const std::string& buffer, + const std::string& contentType, + const HttpHandler::Arguments& cookies) + { + Header header; + PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL); + + for (HttpHandler::Arguments::const_iterator it = cookies.begin(); + it != cookies.end(); it++) + { + header.push_back(std::make_pair("Set-Cookie", it->first + "=" + it->second)); + } + + SendOkHeader(header); + SendString(buffer); + } + + void HttpOutput::AnswerBufferWithContentType(const void* buffer, size_t size, const std::string& contentType) diff -r f579d50fdf8f -r 78a8eaa5f30b Core/HttpServer/HttpOutput.h --- a/Core/HttpServer/HttpOutput.h Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/HttpServer/HttpOutput.h Wed Jan 09 11:41:13 2013 +0100 @@ -32,6 +32,7 @@ #pragma once +#include #include #include #include "../Enumerations.h" @@ -42,8 +43,18 @@ class HttpOutput { private: + typedef std::list< std::pair > Header; + void SendHeaderInternal(Orthanc_HttpStatus status); + void PrepareOkHeader(Header& header, + const char* contentType, + bool hasContentLength, + uint64_t contentLength, + const char* contentFilename); + + void SendOkHeader(const Header& header); + public: virtual ~HttpOutput() { @@ -56,8 +67,6 @@ uint64_t contentLength, const char* contentFilename); - void SendOkHeader(const HttpHandler::Arguments& header); - void SendString(const std::string& s); void SendMethodNotAllowedError(const std::string& allowed); @@ -71,6 +80,10 @@ void AnswerBufferWithContentType(const std::string& buffer, const std::string& contentType); + void AnswerBufferWithContentType(const std::string& buffer, + const std::string& contentType, + const HttpHandler::Arguments& cookies); + void AnswerBufferWithContentType(const void* buffer, size_t size, const std::string& contentType); diff -r f579d50fdf8f -r 78a8eaa5f30b Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/RestApi/RestApi.h Wed Jan 09 11:41:13 2013 +0100 @@ -92,6 +92,11 @@ return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue); } + void ParseCookies(HttpHandler::Arguments& result) const + { + HttpHandler::ParseCookies(result, *httpHeaders_); + } + virtual bool ParseJsonRequest(Json::Value& result) const = 0; }; diff -r f579d50fdf8f -r 78a8eaa5f30b Core/RestApi/RestApiOutput.cpp --- a/Core/RestApi/RestApiOutput.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/RestApi/RestApiOutput.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -32,6 +32,8 @@ #include "RestApiOutput.h" +#include + #include "../OrthancException.h" namespace Orthanc @@ -70,7 +72,7 @@ CheckStatus(); Json::StyledWriter writer; std::string s = writer.write(value); - output_.AnswerBufferWithContentType(s, "application/json"); + output_.AnswerBufferWithContentType(s, "application/json", cookies_); alreadySent_ = true; } @@ -78,7 +80,7 @@ const std::string& contentType) { CheckStatus(); - output_.AnswerBufferWithContentType(buffer, contentType); + output_.AnswerBufferWithContentType(buffer, contentType, cookies_); alreadySent_ = true; } @@ -91,7 +93,8 @@ void RestApiOutput::SignalError(Orthanc_HttpStatus status) { - if (status != Orthanc_HttpStatus_415_UnsupportedMediaType) + if (status != Orthanc_HttpStatus_403_Forbidden && + status != Orthanc_HttpStatus_415_UnsupportedMediaType) { throw OrthancException("This HTTP status is not allowed in a REST API"); } @@ -100,4 +103,36 @@ output_.SendHeader(status); alreadySent_ = true; } + + void RestApiOutput::SetCookie(const std::string& name, + const std::string& value, + unsigned int maxAge) + { + if (name.find(";") != std::string::npos || + name.find(" ") != std::string::npos || + value.find(";") != std::string::npos || + value.find(" ") != std::string::npos) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + CheckStatus(); + + std::string v = value + ";path=/"; + + if (maxAge != 0) + { + v += ";max-age=" + boost::lexical_cast(maxAge); + } + + cookies_[name] = v; + } + + void RestApiOutput::ResetCookie(const std::string& name) + { + // This marks the cookie to be deleted by the browser in 1 second, + // and before it actually gets deleted, its value is set to the + // empty string + SetCookie(name, "", 1); + } } diff -r f579d50fdf8f -r 78a8eaa5f30b Core/RestApi/RestApiOutput.h --- a/Core/RestApi/RestApiOutput.h Tue Jan 08 14:56:10 2013 +0100 +++ b/Core/RestApi/RestApiOutput.h Wed Jan 09 11:41:13 2013 +0100 @@ -44,6 +44,7 @@ private: HttpOutput& output_; bool alreadySent_; + HttpHandler::Arguments cookies_; void CheckStatus(); @@ -72,5 +73,11 @@ void SignalError(Orthanc_HttpStatus status); void Redirect(const std::string& path); + + void SetCookie(const std::string& name, + const std::string& value, + unsigned int maxAge = 0); + + void ResetCookie(const std::string& name); }; } diff -r f579d50fdf8f -r 78a8eaa5f30b UnitTests/RestApi.cpp --- a/UnitTests/RestApi.cpp Tue Jan 08 14:56:10 2013 +0100 +++ b/UnitTests/RestApi.cpp Wed Jan 09 11:41:13 2013 +0100 @@ -10,6 +10,35 @@ using namespace Orthanc; +TEST(RestApi, ParseCookies) +{ + HttpHandler::Arguments headers; + HttpHandler::Arguments cookies; + + headers["cookies"] = "a=b;c=d;;;e=f;;g=h;"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(4u, cookies.size()); + ASSERT_EQ("b", cookies["a"]); + ASSERT_EQ("d", cookies["c"]); + ASSERT_EQ("f", cookies["e"]); + ASSERT_EQ("h", cookies["g"]); + + headers["cookies"] = " name = value ; name2=value2"; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(2u, cookies.size()); + ASSERT_EQ("value", cookies["name"]); + ASSERT_EQ("value2", cookies["name2"]); + + headers["cookies"] = " ;;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(0u, cookies.size()); + + headers["cookies"] = " ; n=v ;; "; + HttpHandler::ParseCookies(cookies, headers); + ASSERT_EQ(1u, cookies.size()); + ASSERT_EQ("v", cookies["n"]); +} + TEST(RestApi, RestApiPath) { RestApiPath::Components args;