summaryrefslogtreecommitdiff
path: root/lib/httpserver/HTTPResponse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/httpserver/HTTPResponse.cpp')
-rw-r--r--lib/httpserver/HTTPResponse.cpp649
1 files changed, 649 insertions, 0 deletions
diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp
new file mode 100644
index 00000000..c56f286f
--- /dev/null
+++ b/lib/httpserver/HTTPResponse.cpp
@@ -0,0 +1,649 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HTTPResponse.cpp
+// Purpose: Response object for HTTP connections
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "HTTPResponse.h"
+#include "IOStreamGetLine.h"
+#include "autogen_HTTPException.h"
+
+#include "MemLeakFindOn.h"
+
+// Static variables
+std::string HTTPResponse::msDefaultURIPrefix;
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::HTTPResponse(IOStream*)
+// Purpose: Constructor for response to be sent to a stream
+// Created: 04/01/09
+//
+// --------------------------------------------------------------------------
+HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo)
+ : mResponseCode(HTTPResponse::Code_NoContent),
+ mResponseIsDynamicContent(true),
+ mKeepAlive(false),
+ mContentLength(-1),
+ mpStreamToSendTo(pStreamToSendTo)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::HTTPResponse()
+// Purpose: Constructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::HTTPResponse()
+ : mResponseCode(HTTPResponse::Code_NoContent),
+ mResponseIsDynamicContent(true),
+ mKeepAlive(false),
+ mContentLength(-1),
+ mpStreamToSendTo(NULL)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::~HTTPResponse()
+// Purpose: Destructor
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+HTTPResponse::~HTTPResponse()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::ResponseCodeToString(int)
+// Purpose: Return string equivalent of the response code,
+// suitable for Status: headers
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+const char *HTTPResponse::ResponseCodeToString(int ResponseCode)
+{
+ switch(ResponseCode)
+ {
+ case Code_OK: return "200 OK"; break;
+ case Code_NoContent: return "204 No Content"; break;
+ case Code_MovedPermanently: return "301 Moved Permanently"; break;
+ case Code_Found: return "302 Found"; break;
+ case Code_NotModified: return "304 Not Modified"; break;
+ case Code_TemporaryRedirect: return "307 Temporary Redirect"; break;
+ case Code_MethodNotAllowed: return "400 Method Not Allowed"; break;
+ case Code_Unauthorized: return "401 Unauthorized"; break;
+ case Code_Forbidden: return "403 Forbidden"; break;
+ case Code_NotFound: return "404 Not Found"; break;
+ case Code_InternalServerError: return "500 Internal Server Error"; break;
+ case Code_NotImplemented: return "501 Not Implemented"; break;
+ default:
+ {
+ THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed)
+ }
+ }
+ return "500 Internal Server Error";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetResponseCode(int)
+// Purpose: Set the response code to be returned
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetResponseCode(int Code)
+{
+ mResponseCode = Code;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetContentType(const char *)
+// Purpose: Set content type
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetContentType(const char *ContentType)
+{
+ mContentType = ContentType;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::Send(IOStream &, bool)
+// Purpose: Build the response, and send via the stream.
+// Created: 26/3/2004
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::Send(bool OmitContent)
+{
+ if (!mpStreamToSendTo)
+ {
+ THROW_EXCEPTION(HTTPException, NoStreamConfigured);
+ }
+
+ if (GetSize() != 0 && mContentType.empty())
+ {
+ THROW_EXCEPTION(HTTPException, NoContentTypeSet);
+ }
+
+ // Build and send header
+ {
+ std::string header("HTTP/1.1 ");
+ header += ResponseCodeToString(mResponseCode);
+ header += "\r\nContent-Type: ";
+ header += mContentType;
+ header += "\r\nContent-Length: ";
+ {
+ char len[32];
+ ::sprintf(len, "%d", OmitContent?(0):(GetSize()));
+ header += len;
+ }
+ // Extra headers...
+ for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
+ {
+ header += "\r\n";
+ header += i->first + ": " + i->second;
+ }
+ // NOTE: a line ending must be included here in all cases
+ // Control whether the response is cached
+ if(mResponseIsDynamicContent)
+ {
+ // dynamic is private and can't be cached
+ header += "\r\nCache-Control: no-cache, private";
+ }
+ else
+ {
+ // static is allowed to be cached for a day
+ header += "\r\nCache-Control: max-age=86400";
+ }
+
+ if(mKeepAlive)
+ {
+ header += "\r\nConnection: keep-alive\r\n\r\n";
+ }
+ else
+ {
+ header += "\r\nConnection: close\r\n\r\n";
+ }
+
+ // NOTE: header ends with blank line in all cases
+
+ // Write to stream
+ mpStreamToSendTo->Write(header.c_str(), header.size());
+ }
+
+ // Send content
+ if(!OmitContent)
+ {
+ mpStreamToSendTo->Write(GetBuffer(), GetSize());
+ }
+}
+
+void HTTPResponse::SendContinue()
+{
+ mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n");
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int)
+// Purpose: Private. Parse the headers of the response
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
+{
+ std::string header;
+ bool haveHeader = false;
+ while(true)
+ {
+ if(rGetLine.IsEOF())
+ {
+ // Header terminates unexpectedly
+ THROW_EXCEPTION(HTTPException, BadRequest)
+ }
+
+ std::string currentLine;
+ if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
+ {
+ // Timeout
+ THROW_EXCEPTION(HTTPException, RequestReadFailed)
+ }
+
+ // Is this a continuation of the previous line?
+ bool processHeader = haveHeader;
+ if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
+ {
+ // A continuation, don't process anything yet
+ processHeader = false;
+ }
+ //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
+
+ // Parse the header -- this will actually process the header
+ // from the previous run around the loop.
+ if(processHeader)
+ {
+ // Find where the : is in the line
+ const char *h = header.c_str();
+ int p = 0;
+ while(h[p] != '\0' && h[p] != ':')
+ {
+ ++p;
+ }
+ // Skip white space
+ int dataStart = p + 1;
+ while(h[dataStart] == ' ' || h[dataStart] == '\t')
+ {
+ ++dataStart;
+ }
+
+ if(p == sizeof("Content-Length")-1
+ && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0)
+ {
+ // Decode number
+ long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK
+ if(len < 0) len = 0;
+ // Store
+ mContentLength = len;
+ }
+ else if(p == sizeof("Content-Type")-1
+ && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0)
+ {
+ // Store rest of string as content type
+ mContentType = h + dataStart;
+ }
+ else if(p == sizeof("Cookie")-1
+ && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0)
+ {
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ /*
+ // Parse cookies
+ ParseCookies(header, dataStart);
+ */
+ }
+ else if(p == sizeof("Connection")-1
+ && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0)
+ {
+ // Connection header, what is required?
+ const char *v = h + dataStart;
+ if(::strcasecmp(v, "close") == 0)
+ {
+ mKeepAlive = false;
+ }
+ else if(::strcasecmp(v, "keep-alive") == 0)
+ {
+ mKeepAlive = true;
+ }
+ // else don't understand, just assume default for protocol version
+ }
+ else
+ {
+ std::string headerName = header.substr(0, p);
+ AddHeader(headerName, h + dataStart);
+ }
+
+ // Unset have header flag, as it's now been processed
+ haveHeader = false;
+ }
+
+ // Store the chunk of header the for next time round
+ if(haveHeader)
+ {
+ header += currentLine;
+ }
+ else
+ {
+ header = currentLine;
+ haveHeader = true;
+ }
+
+ // End of headers?
+ if(currentLine.empty())
+ {
+ // All done!
+ break;
+ }
+ }
+}
+
+void HTTPResponse::Receive(IOStream& rStream, int Timeout)
+{
+ IOStreamGetLine rGetLine(rStream);
+
+ if(rGetLine.IsEOF())
+ {
+ // Connection terminated unexpectedly
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse,
+ "HTTP server closed the connection without sending a response");
+ }
+
+ std::string statusLine;
+ if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout))
+ {
+ // Timeout
+ THROW_EXCEPTION_MESSAGE(HTTPException, ResponseReadFailed,
+ "Failed to get a response from the HTTP server within the timeout");
+ }
+
+ if (statusLine.substr(0, 7) != "HTTP/1." || statusLine[8] != ' ')
+ {
+ THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse,
+ "HTTP server sent an invalid HTTP status line: " << statusLine);
+ }
+
+ if (statusLine[5] == '1' && statusLine[7] == '1')
+ {
+ // HTTP/1.1 default is to keep alive
+ mKeepAlive = true;
+ }
+
+ // Decode the status code
+ long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10);
+ // returns zero in error case, this is OK
+ if (status < 0) status = 0;
+ // Store
+ mResponseCode = status;
+
+ // 100 Continue responses have no headers, terminating newline, or body
+ if (status == 100)
+ {
+ return;
+ }
+
+ ParseHeaders(rGetLine, Timeout);
+
+ // push back whatever bytes we have left
+ // rGetLine.DetachFile();
+ if (mContentLength > 0)
+ {
+ if (mContentLength < rGetLine.GetSizeOfBufferedData())
+ {
+ // very small response, not good!
+ THROW_EXCEPTION(HTTPException, NotImplemented);
+ }
+
+ mContentLength -= rGetLine.GetSizeOfBufferedData();
+
+ Write(rGetLine.GetBufferedData(),
+ rGetLine.GetSizeOfBufferedData());
+ }
+
+ while (mContentLength != 0) // could be -1 as well
+ {
+ char buffer[4096];
+ int readSize = sizeof(buffer);
+ if (mContentLength > 0 && mContentLength < readSize)
+ {
+ readSize = mContentLength;
+ }
+ readSize = rStream.Read(buffer, readSize, Timeout);
+ if (readSize == 0)
+ {
+ break;
+ }
+ mContentLength -= readSize;
+ Write(buffer, readSize);
+ }
+
+ SetForReading();
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const char *)
+// Purpose: Add header, given entire line
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+/*
+void HTTPResponse::AddHeader(const char *EntireHeaderLine)
+{
+ mExtraHeaders.push_back(std::string(EntireHeaderLine));
+}
+*/
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const std::string &)
+// Purpose: Add header, given entire line
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+/*
+void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
+{
+ mExtraHeaders.push_back(rEntireHeaderLine);
+}
+*/
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const char *, const char *)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *pHeader, const char *pValue)
+{
+ mExtraHeaders.push_back(Header(pHeader, pValue));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const char *, const std::string &)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue)
+{
+ mExtraHeaders.push_back(Header(pHeader, rValue));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::AddHeader(const std::string &, const std::string &)
+// Purpose: Add header, given header name and it's value
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
+{
+ mExtraHeaders.push_back(Header(rHeader, rValue));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetCookie(const char *, const char *, const char *, int)
+// Purpose: Sets a cookie, using name, value, path and expiry time.
+// Created: 20/8/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt)
+{
+ if(ExpiresAt != 0)
+ {
+ THROW_EXCEPTION(HTTPException, NotImplemented)
+ }
+
+ // Appears you shouldn't use quotes when you generate set-cookie headers.
+ // Oh well. It was fun finding that out.
+/* std::string h("Set-Cookie: ");
+ h += Name;
+ h += "=\"";
+ h += Value;
+ h += "\"; Version=\"1\"; Path=\"";
+ h += Path;
+ h += "\"";
+*/
+ std::string h;
+ h += Name;
+ h += "=";
+ h += Value;
+ h += "; Version=1; Path=";
+ h += Path;
+
+ mExtraHeaders.push_back(Header("Set-Cookie", h));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetAsRedirect(const char *, bool)
+// Purpose: Sets the response objects to be a redirect to another page.
+// If IsLocalURL == true, the default prefix will be added.
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
+{
+ if(mResponseCode != HTTPResponse::Code_NoContent
+ || !mContentType.empty()
+ || GetSize() != 0)
+ {
+ THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData)
+ }
+
+ // Set response code
+ mResponseCode = Code_Found;
+
+ // Set location to redirect to
+ std::string header;
+ if(IsLocalURI) header += msDefaultURIPrefix;
+ header += RedirectTo;
+ mExtraHeaders.push_back(Header("Location", header));
+
+ // Set up some default content
+ mContentType = "text/html";
+ #define REDIRECT_HTML_1 "<html><head><title>Redirection</title></head>\n<body><p><a href=\""
+ #define REDIRECT_HTML_2 "\">Redirect to content</a></p></body></html>\n"
+ Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1);
+ if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size());
+ Write(RedirectTo, ::strlen(RedirectTo));
+ Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::SetAsNotFound(const char *)
+// Purpose: Set the response object to be a standard page not found 404 response.
+// Created: 7/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::SetAsNotFound(const char *URI)
+{
+ if(mResponseCode != HTTPResponse::Code_NoContent
+ || mExtraHeaders.size() != 0
+ || !mContentType.empty()
+ || GetSize() != 0)
+ {
+ THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData)
+ }
+
+ // Set response code
+ mResponseCode = Code_NotFound;
+
+ // Set data
+ mContentType = "text/html";
+ #define NOT_FOUND_HTML_1 "<html><head><title>404 Not Found</title></head>\n<body><h1>404 Not Found</h1>\n<p>The URI <i>"
+ #define NOT_FOUND_HTML_2 "</i> was not found on this server.</p></body></html>\n"
+ Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1);
+ WriteStringDefang(std::string(URI));
+ Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HTTPResponse::WriteStringDefang(const char *, unsigned int)
+// Purpose: Writes a string 'defanged', ie has HTML special characters escaped
+// so that people can't output arbitary HTML by playing with
+// URLs and form parameters, and it's safe to write strings into
+// HTML element attribute values.
+// Created: 9/4/04
+//
+// --------------------------------------------------------------------------
+void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen)
+{
+ while(StringLen > 0)
+ {
+ unsigned int toWrite = 0;
+ while(toWrite < StringLen
+ && String[toWrite] != '<'
+ && String[toWrite] != '>'
+ && String[toWrite] != '&'
+ && String[toWrite] != '"')
+ {
+ ++toWrite;
+ }
+ if(toWrite > 0)
+ {
+ Write(String, toWrite);
+ StringLen -= toWrite;
+ String += toWrite;
+ }
+
+ // Is it a bad character next?
+ while(StringLen > 0)
+ {
+ bool notSpecial = false;
+ switch(*String)
+ {
+ case '<': Write("&lt;", 4); break;
+ case '>': Write("&gt;", 4); break;
+ case '&': Write("&amp;", 5); break;
+ case '"': Write("&quot;", 6); break;
+ default:
+ // Stop this loop
+ notSpecial = true;
+ break;
+ }
+ if(notSpecial) break;
+ ++String;
+ --StringLen;
+ }
+ }
+}
+
+