source: box/trunk/lib/httpserver/HTTPResponse.cpp @ 2436

Revision 2436, 17.1 KB checked in by chris, 3 years ago (diff)

Add support for sending an HTTP/1.0 100 Continue response during
processing of a request by HTTPServer, by keeping a pointer to the
socket object.

Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    HTTPResponse.cpp
5//              Purpose: Response object for HTTP connections
6//              Created: 26/3/04
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#include <stdio.h>
13#include <string.h>
14
15#include "HTTPResponse.h"
16#include "IOStreamGetLine.h"
17#include "autogen_HTTPException.h"
18
19#include "MemLeakFindOn.h"
20
21// Static variables
22std::string HTTPResponse::msDefaultURIPrefix;
23
24
25// --------------------------------------------------------------------------
26//
27// Function
28//              Name:    HTTPResponse::HTTPResponse(IOStream*)
29//              Purpose: Constructor for response to be sent to a stream
30//              Created: 04/01/09
31//
32// --------------------------------------------------------------------------
33HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo)
34        : mResponseCode(HTTPResponse::Code_NoContent),
35          mResponseIsDynamicContent(true),
36          mKeepAlive(false),
37          mContentLength(-1),
38          mpStreamToSendTo(pStreamToSendTo)
39{
40}
41
42
43// --------------------------------------------------------------------------
44//
45// Function
46//              Name:    HTTPResponse::HTTPResponse()
47//              Purpose: Constructor
48//              Created: 26/3/04
49//
50// --------------------------------------------------------------------------
51HTTPResponse::HTTPResponse()
52        : mResponseCode(HTTPResponse::Code_NoContent),
53          mResponseIsDynamicContent(true),
54          mKeepAlive(false),
55          mContentLength(-1),
56          mpStreamToSendTo(NULL)
57{
58}
59
60
61// --------------------------------------------------------------------------
62//
63// Function
64//              Name:    HTTPResponse::~HTTPResponse()
65//              Purpose: Destructor
66//              Created: 26/3/04
67//
68// --------------------------------------------------------------------------
69HTTPResponse::~HTTPResponse()
70{
71}
72
73
74// --------------------------------------------------------------------------
75//
76// Function
77//              Name:    HTTPResponse::ResponseCodeToString(int)
78//              Purpose: Return string equivalent of the response code,
79//                       suitable for Status: headers
80//              Created: 26/3/04
81//
82// --------------------------------------------------------------------------
83const char *HTTPResponse::ResponseCodeToString(int ResponseCode)
84{
85        switch(ResponseCode)
86        {
87        case Code_OK: return "200 OK"; break;
88        case Code_NoContent: return "204 No Content"; break;
89        case Code_MovedPermanently: return "301 Moved Permanently"; break;
90        case Code_Found: return "302 Found"; break;
91        case Code_NotModified: return "304 Not Modified"; break;
92        case Code_TemporaryRedirect: return "307 Temporary Redirect"; break;
93        case Code_MethodNotAllowed: return "400 Method Not Allowed"; break;
94        case Code_Unauthorized: return "401 Unauthorized"; break;
95        case Code_Forbidden: return "403 Forbidden"; break;
96        case Code_NotFound: return "404 Not Found"; break;
97        case Code_InternalServerError: return "500 Internal Server Error"; break;
98        case Code_NotImplemented: return "501 Not Implemented"; break;
99        default:
100                {
101                        THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed)
102                }
103        }
104        return "500 Internal Server Error";
105}
106
107
108// --------------------------------------------------------------------------
109//
110// Function
111//              Name:    HTTPResponse::SetResponseCode(int)
112//              Purpose: Set the response code to be returned
113//              Created: 26/3/04
114//
115// --------------------------------------------------------------------------
116void HTTPResponse::SetResponseCode(int Code)
117{
118        mResponseCode = Code;
119}
120
121
122// --------------------------------------------------------------------------
123//
124// Function
125//              Name:    HTTPResponse::SetContentType(const char *)
126//              Purpose: Set content type
127//              Created: 26/3/04
128//
129// --------------------------------------------------------------------------
130void HTTPResponse::SetContentType(const char *ContentType)
131{
132        mContentType = ContentType;
133}
134
135
136// --------------------------------------------------------------------------
137//
138// Function
139//              Name:    HTTPResponse::Send(IOStream &, bool)
140//              Purpose: Build the response, and send via the stream.
141//                       Optionally omitting the content.
142//              Created: 26/3/04
143//
144// --------------------------------------------------------------------------
145void HTTPResponse::Send(bool OmitContent)
146{
147        if (!mpStreamToSendTo)
148        {
149                THROW_EXCEPTION(HTTPException, NoStreamConfigured);
150        }
151       
152        if (GetSize() != 0 && mContentType.empty())
153        {
154                THROW_EXCEPTION(HTTPException, NoContentTypeSet);
155        }
156
157        // Build and send header
158        {
159                std::string header("HTTP/1.1 ");
160                header += ResponseCodeToString(mResponseCode);
161                header += "\r\nContent-Type: ";
162                header += mContentType;
163                header += "\r\nContent-Length: ";
164                {
165                        char len[32];
166                        ::sprintf(len, "%d", OmitContent?(0):(GetSize()));
167                        header += len;
168                }
169                // Extra headers...
170                for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)
171                {
172                        header += "\r\n";
173                        header += i->first + ": " + i->second;
174                }
175                // NOTE: a line ending must be included here in all cases
176                // Control whether the response is cached
177                if(mResponseIsDynamicContent)
178                {
179                        // dynamic is private and can't be cached
180                        header += "\r\nCache-Control: no-cache, private";
181                }
182                else
183                {
184                        // static is allowed to be cached for a day
185                        header += "\r\nCache-Control: max-age=86400";
186                }
187                if(mKeepAlive)
188                {
189                        header += "\r\nConnection: keep-alive\r\n\r\n";
190                }
191                else
192                {
193                        header += "\r\nConnection: close\r\n\r\n";
194                }
195                // NOTE: header ends with blank line in all cases
196               
197                // Write to stream
198                mpStreamToSendTo->Write(header.c_str(), header.size());
199        }
200       
201        // Send content
202        if(!OmitContent)
203        {
204                mpStreamToSendTo->Write(GetBuffer(), GetSize());
205        }
206}
207
208void HTTPResponse::SendContinue()
209{
210        mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n");
211}
212
213// --------------------------------------------------------------------------
214//
215// Function
216//              Name:    HTTPResponse::ParseHeaders(IOStreamGetLine &, int)
217//              Purpose: Private. Parse the headers of the response
218//              Created: 26/3/04
219//
220// --------------------------------------------------------------------------
221void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
222{
223        std::string header;
224        bool haveHeader = false;
225        while(true)
226        {
227                if(rGetLine.IsEOF())
228                {
229                        // Header terminates unexpectedly
230                        THROW_EXCEPTION(HTTPException, BadRequest)             
231                }
232
233                std::string currentLine;       
234                if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
235                {
236                        // Timeout
237                        THROW_EXCEPTION(HTTPException, RequestReadFailed)
238                }
239               
240                // Is this a continuation of the previous line?
241                bool processHeader = haveHeader;
242                if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
243                {
244                        // A continuation, don't process anything yet
245                        processHeader = false;
246                }
247                //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
248               
249                // Parse the header -- this will actually process the header
250                // from the previous run around the loop.
251                if(processHeader)
252                {
253                        // Find where the : is in the line
254                        const char *h = header.c_str();
255                        int p = 0;
256                        while(h[p] != '\0' && h[p] != ':')
257                        {
258                                ++p;
259                        }
260                        // Skip white space
261                        int dataStart = p + 1;
262                        while(h[dataStart] == ' ' || h[dataStart] == '\t')
263                        {
264                                ++dataStart;
265                        }
266               
267                        if(p == sizeof("Content-Length")-1
268                                && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0)
269                        {
270                                // Decode number
271                                long len = ::strtol(h + dataStart, NULL, 10);   // returns zero in error case, this is OK
272                                if(len < 0) len = 0;
273                                // Store
274                                mContentLength = len;
275                        }
276                        else if(p == sizeof("Content-Type")-1
277                                && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0)
278                        {
279                                // Store rest of string as content type
280                                mContentType = h + dataStart;
281                        }
282                        else if(p == sizeof("Cookie")-1
283                                && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0)
284                        {
285                                THROW_EXCEPTION(HTTPException, NotImplemented);
286                                /*
287                                // Parse cookies
288                                ParseCookies(header, dataStart);
289                                */
290                        }
291                        else if(p == sizeof("Connection")-1
292                                && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0)
293                        {
294                                // Connection header, what is required?
295                                const char *v = h + dataStart;
296                                if(::strcasecmp(v, "close") == 0)
297                                {
298                                        mKeepAlive = false;
299                                }
300                                else if(::strcasecmp(v, "keep-alive") == 0)
301                                {
302                                        mKeepAlive = true;
303                                }
304                                // else don't understand, just assume default for protocol version
305                        }
306                        else
307                        {
308                                std::string headerName = header.substr(0, p);
309                                AddHeader(headerName, h + dataStart);
310                        }
311                       
312                        // Unset have header flag, as it's now been processed
313                        haveHeader = false;
314                }
315
316                // Store the chunk of header the for next time round
317                if(haveHeader)
318                {
319                        header += currentLine;
320                }
321                else
322                {
323                        header = currentLine;
324                        haveHeader = true;
325                }
326
327                // End of headers?
328                if(currentLine.empty())
329                {
330                        // All done!
331                        break;
332                }               
333        }
334}
335
336void HTTPResponse::Receive(IOStream& rStream, int Timeout)
337{
338        IOStreamGetLine rGetLine(rStream);
339
340        if(rGetLine.IsEOF())
341        {
342                // Connection terminated unexpectedly
343                THROW_EXCEPTION(HTTPException, BadResponse)             
344        }
345
346        std::string statusLine; 
347        if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout))
348        {
349                // Timeout
350                THROW_EXCEPTION(HTTPException, ResponseReadFailed)
351        }
352
353        if (statusLine.substr(0, 7) != "HTTP/1." ||
354                statusLine[8] != ' ')
355        {
356                // Status line terminated unexpectedly
357                BOX_ERROR("Bad response status line: " << statusLine);
358                THROW_EXCEPTION(HTTPException, BadResponse)             
359        }
360
361        if (statusLine[5] == '1' && statusLine[7] == '1')
362        {
363                // HTTP/1.1 default is to keep alive
364                mKeepAlive = true;
365        }
366               
367        // Decode the status code
368        long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10);
369        // returns zero in error case, this is OK
370        if (status < 0) status = 0;
371        // Store
372        mResponseCode = status;
373
374        // 100 Continue responses have no headers, terminating newline, or body
375        if (status == 100)
376        {
377                return;
378        }
379       
380        ParseHeaders(rGetLine, Timeout);
381
382        // push back whatever bytes we have left
383        // rGetLine.DetachFile();
384        if (mContentLength > 0)
385        {
386                if (mContentLength < rGetLine.GetSizeOfBufferedData())
387                {
388                        // very small response, not good!
389                        THROW_EXCEPTION(HTTPException, NotImplemented);
390                }
391
392                mContentLength -= rGetLine.GetSizeOfBufferedData();
393
394                Write(rGetLine.GetBufferedData(),
395                        rGetLine.GetSizeOfBufferedData());
396        }
397
398        while (mContentLength != 0) // could be -1 as well
399        {
400                char buffer[4096];
401                int readSize = sizeof(buffer);
402                if (mContentLength > 0 && mContentLength < readSize)
403                {
404                        readSize = mContentLength;
405                }
406                readSize = rStream.Read(buffer, readSize, Timeout);
407                if (readSize == 0)
408                {
409                        break;
410                }
411                mContentLength -= readSize;
412                Write(buffer, readSize);
413        }
414
415        SetForReading();
416}
417
418// --------------------------------------------------------------------------
419//
420// Function
421//              Name:    HTTPResponse::AddHeader(const char *)
422//              Purpose: Add header, given entire line
423//              Created: 26/3/04
424//
425// --------------------------------------------------------------------------
426/*
427void HTTPResponse::AddHeader(const char *EntireHeaderLine)
428{
429        mExtraHeaders.push_back(std::string(EntireHeaderLine));
430}
431*/
432
433// --------------------------------------------------------------------------
434//
435// Function
436//              Name:    HTTPResponse::AddHeader(const std::string &)
437//              Purpose: Add header, given entire line
438//              Created: 26/3/04
439//
440// --------------------------------------------------------------------------
441/*
442void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine)
443{
444        mExtraHeaders.push_back(rEntireHeaderLine);
445}
446*/
447
448// --------------------------------------------------------------------------
449//
450// Function
451//              Name:    HTTPResponse::AddHeader(const char *, const char *)
452//              Purpose: Add header, given header name and it's value
453//              Created: 26/3/04
454//
455// --------------------------------------------------------------------------
456void HTTPResponse::AddHeader(const char *pHeader, const char *pValue)
457{
458        mExtraHeaders.push_back(Header(pHeader, pValue));
459}
460
461
462// --------------------------------------------------------------------------
463//
464// Function
465//              Name:    HTTPResponse::AddHeader(const char *, const std::string &)
466//              Purpose: Add header, given header name and it's value
467//              Created: 26/3/04
468//
469// --------------------------------------------------------------------------
470void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue)
471{
472        mExtraHeaders.push_back(Header(pHeader, rValue));
473}
474
475
476// --------------------------------------------------------------------------
477//
478// Function
479//              Name:    HTTPResponse::AddHeader(const std::string &, const std::string &)
480//              Purpose: Add header, given header name and it's value
481//              Created: 26/3/04
482//
483// --------------------------------------------------------------------------
484void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue)
485{
486        mExtraHeaders.push_back(Header(rHeader, rValue));
487}
488
489
490// --------------------------------------------------------------------------
491//
492// Function
493//              Name:    HTTPResponse::SetCookie(const char *, const char *, const char *, int)
494//              Purpose: Sets a cookie, using name, value, path and expiry time.
495//              Created: 20/8/04
496//
497// --------------------------------------------------------------------------
498void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt)
499{
500        if(ExpiresAt != 0)
501        {
502                THROW_EXCEPTION(HTTPException, NotImplemented)
503        }
504
505        // Appears you shouldn't use quotes when you generate set-cookie headers.
506        // Oh well. It was fun finding that out.
507/*      std::string h("Set-Cookie: ");
508        h += Name;
509        h += "=\"";
510        h += Value;
511        h += "\"; Version=\"1\"; Path=\"";
512        h += Path;
513        h += "\"";
514*/
515        std::string h;
516        h += Name;
517        h += "=";
518        h += Value;
519        h += "; Version=1; Path=";
520        h += Path;
521
522        mExtraHeaders.push_back(Header("Set-Cookie", h));
523}
524
525
526// --------------------------------------------------------------------------
527//
528// Function
529//              Name:    HTTPResponse::SetAsRedirect(const char *, bool)
530//              Purpose: Sets the response objects to be a redirect to another page.
531//                               If IsLocalURL == true, the default prefix will be added.
532//              Created: 26/3/04
533//
534// --------------------------------------------------------------------------
535void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI)
536{
537        if(mResponseCode != HTTPResponse::Code_NoContent
538                || !mContentType.empty()
539                || GetSize() != 0)
540        {
541                THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData)
542        }
543
544        // Set response code
545        mResponseCode = Code_Found;
546
547        // Set location to redirect to
548        std::string header;
549        if(IsLocalURI) header += msDefaultURIPrefix;
550        header += RedirectTo;
551        mExtraHeaders.push_back(Header("Location", header));
552       
553        // Set up some default content
554        mContentType = "text/html";
555        #define REDIRECT_HTML_1 "<html><head><title>Redirection</title></head>\n<body><p><a href=\""
556        #define REDIRECT_HTML_2 "\">Redirect to content</a></p></body></html>\n"
557        Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1);
558        if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size());
559        Write(RedirectTo, ::strlen(RedirectTo));
560        Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1);
561}
562
563
564// --------------------------------------------------------------------------
565//
566// Function
567//              Name:    HTTPResponse::SetAsNotFound(const char *)
568//              Purpose: Set the response object to be a standard page not found 404 response.
569//              Created: 7/4/04
570//
571// --------------------------------------------------------------------------
572void HTTPResponse::SetAsNotFound(const char *URI)
573{
574        if(mResponseCode != HTTPResponse::Code_NoContent
575                || mExtraHeaders.size() != 0
576                || !mContentType.empty()
577                || GetSize() != 0)
578        {
579                THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData)
580        }
581
582        // Set response code
583        mResponseCode = Code_NotFound;
584
585        // Set data
586        mContentType = "text/html";
587        #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>"
588        #define NOT_FOUND_HTML_2 "</i> was not found on this server.</p></body></html>\n"
589        Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1);
590        WriteStringDefang(std::string(URI));
591        Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1);
592}
593
594
595// --------------------------------------------------------------------------
596//
597// Function
598//              Name:    HTTPResponse::WriteStringDefang(const char *, unsigned int)
599//              Purpose: Writes a string 'defanged', ie has HTML special characters escaped
600//                               so that people can't output arbitary HTML by playing with
601//                               URLs and form parameters, and it's safe to write strings into
602//                               HTML element attribute values.
603//              Created: 9/4/04
604//
605// --------------------------------------------------------------------------
606void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen)
607{
608        while(StringLen > 0)
609        {
610                unsigned int toWrite = 0;
611                while(toWrite < StringLen
612                        && String[toWrite] != '<' 
613                        && String[toWrite] != '>'
614                        && String[toWrite] != '&'
615                        && String[toWrite] != '"')
616                {
617                        ++toWrite;
618                }
619                if(toWrite > 0)
620                {
621                        Write(String, toWrite);
622                        StringLen -= toWrite;
623                        String += toWrite;
624                }
625               
626                // Is it a bad character next?
627                while(StringLen > 0)
628                {
629                        bool notSpecial = false;
630                        switch(*String)
631                        {
632                                case '<': Write("&lt;", 4); break;
633                                case '>': Write("&gt;", 4); break;
634                                case '&': Write("&amp;", 5); break;
635                                case '"': Write("&quot;", 6); break;
636                                default:
637                                        // Stop this loop
638                                        notSpecial = true;
639                                        break;
640                        }
641                        if(notSpecial) break;
642                        ++String;
643                        --StringLen;
644                }
645        }
646}
647
648
Note: See TracBrowser for help on using the repository browser.