changeset 330:78a8eaa5f30b

cookies
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 09 Jan 2013 11:41:13 +0100
parents f579d50fdf8f
children 5a96dac27959
files Core/HttpServer/FilesystemHttpHandler.cpp Core/HttpServer/HttpFileSender.cpp Core/HttpServer/HttpHandler.cpp Core/HttpServer/HttpHandler.h Core/HttpServer/HttpOutput.cpp Core/HttpServer/HttpOutput.h Core/RestApi/RestApi.h Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApiOutput.h UnitTests/RestApi.cpp
diffstat 10 files changed, 187 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- 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("<html>");
     output.SendString("  <body>");
     output.SendString("    <h1>Subdirectories</h1>");
--- 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<std::string>(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)
--- 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;
+        }
+      }
+    }
+  }
 }
--- 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);
   };
 }
--- 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<std::string>(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<std::string>(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)
--- 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 <list>
 #include <string>
 #include <stdint.h>
 #include "../Enumerations.h"
@@ -42,8 +43,18 @@
   class HttpOutput
   {
   private:
+    typedef std::list< std::pair<std::string, std::string> >  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);
--- 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;
     };
 
--- 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 <boost/lexical_cast.hpp>
+
 #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<std::string>(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);
+  }
 }
--- 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);
   };
 }
--- 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;