source: box/trunk/lib/httpserver/HTTPRequest.cpp @ 2504

Revision 2504, 17.8 KB checked in by chris, 3 years ago (diff)

Move S3Simulator into its own class, like S3Client, for reuse elsewhere.

Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    HTTPRequest.cpp
5//              Purpose: Request object for HTTP connections
6//              Created: 26/3/04
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#include <string.h>
13#include <strings.h>
14#include <stdlib.h>
15#include <stdio.h>
16
17#include "HTTPRequest.h"
18#include "HTTPResponse.h"
19#include "HTTPQueryDecoder.h"
20#include "autogen_HTTPException.h"
21#include "IOStream.h"
22#include "IOStreamGetLine.h"
23#include "Logging.h"
24
25#include "MemLeakFindOn.h"
26
27#define MAX_CONTENT_SIZE        (128*1024)
28
29#define ENSURE_COOKIE_JAR_ALLOCATED \
30        if(mpCookies == 0) {mpCookies = new CookieJar_t;}
31
32
33
34// --------------------------------------------------------------------------
35//
36// Function
37//              Name:    HTTPRequest::HTTPRequest()
38//              Purpose: Constructor
39//              Created: 26/3/04
40//
41// --------------------------------------------------------------------------
42HTTPRequest::HTTPRequest()
43        : mMethod(Method_UNINITIALISED),
44          mHostPort(80),        // default if not specified
45          mHTTPVersion(0),
46          mContentLength(-1),
47          mpCookies(0),
48          mClientKeepAliveRequested(false),
49          mExpectContinue(false),
50          mpStreamToReadFrom(NULL)
51{
52}
53
54
55// --------------------------------------------------------------------------
56//
57// Function
58//              Name:    HTTPRequest::HTTPRequest(enum Method,
59//                       const std::string&)
60//              Purpose: Alternate constructor for hand-crafted requests
61//              Created: 03/01/09
62//
63// --------------------------------------------------------------------------
64HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI)
65        : mMethod(method),
66          mRequestURI(rURI),
67          mHostPort(80), // default if not specified
68          mHTTPVersion(HTTPVersion_1_1),
69          mContentLength(-1),
70          mpCookies(0),
71          mClientKeepAliveRequested(false),
72          mExpectContinue(false),
73          mpStreamToReadFrom(NULL)
74{
75}
76
77
78
79// --------------------------------------------------------------------------
80//
81// Function
82//              Name:    HTTPRequest::~HTTPRequest()
83//              Purpose: Destructor
84//              Created: 26/3/04
85//
86// --------------------------------------------------------------------------
87HTTPRequest::~HTTPRequest()
88{
89        // Clean up any cookies
90        if(mpCookies != 0)
91        {
92                delete mpCookies;
93                mpCookies = 0;
94        }
95}
96
97
98// --------------------------------------------------------------------------
99//
100// Function
101//              Name:    HTTPRequest::Receive(IOStreamGetLine &, int)
102//              Purpose: Read the request from an IOStreamGetLine (and
103//                       attached stream).
104//                       Returns false if there was no valid request,
105//                       probably due to a kept-alive connection closing.
106//              Created: 26/3/04
107//
108// --------------------------------------------------------------------------
109bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout)
110{
111        // Check caller's logic
112        if(mMethod != Method_UNINITIALISED)
113        {
114                THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead)
115        }
116
117        // Read the first line, which is of a different format to the rest of the lines
118        std::string requestLine;
119        if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout))
120        {
121                // Didn't get the request line, probably end of connection which had been kept alive
122                return false;
123        }
124        BOX_TRACE("Request line: " << requestLine);
125
126        // Check the method
127        size_t p = 0;   // current position in string
128        p = requestLine.find(' '); // end of first word
129       
130        if (p == std::string::npos)
131        {
132                // No terminating space, looks bad
133                p = requestLine.size();
134        }
135        else
136        {
137                mHttpVerb = requestLine.substr(0, p);
138                if (mHttpVerb == "GET")
139                {
140                        mMethod = Method_GET;
141                }
142                else if (mHttpVerb == "HEAD")
143                {
144                        mMethod = Method_HEAD;
145                }
146                else if (mHttpVerb == "POST")
147                {
148                        mMethod = Method_POST;
149                }
150                else if (mHttpVerb == "PUT")
151                {
152                        mMethod = Method_PUT;
153                }
154                else
155                {
156                        mMethod = Method_UNKNOWN;
157                }
158        }
159
160        // Skip spaces to find URI
161        const char *requestLinePtr = requestLine.c_str();
162        while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
163        {
164                ++p;
165        }
166       
167        // Check there's a URI following...
168        if(requestLinePtr[p] == '\0')
169        {
170                // Didn't get the request line, probably end of connection which had been kept alive
171                return false;
172        }
173       
174        // Read the URI, unescaping any %XX hex codes
175        while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
176        {
177                // End of URI, on to query string?
178                if(requestLinePtr[p] == '?')
179                {
180                        // Put the rest into the query string, without escaping anything
181                        ++p;
182                        while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0')
183                        {
184                                mQueryString += requestLinePtr[p];
185                                ++p;
186                        }
187                        break;
188                }
189                // Needs unescaping?
190                else if(requestLinePtr[p] == '+')
191                {
192                        mRequestURI += ' ';
193                }
194                else if(requestLinePtr[p] == '%')
195                {
196                        // Be tolerant about this... bad things are silently accepted,
197                        // rather than throwing an error.
198                        char code[4] = {0,0,0,0};
199                        code[0] = requestLinePtr[++p];
200                        if(code[0] != '\0')
201                        {
202                                code[1] = requestLinePtr[++p];
203                        }
204                       
205                        // Convert into a char code
206                        long c = ::strtol(code, NULL, 16);
207                       
208                        // Accept it?
209                        if(c > 0 && c <= 255)
210                        {
211                                mRequestURI += (char)c;
212                        }
213                }
214                else
215                {
216                        // Simple copy of character
217                        mRequestURI += requestLinePtr[p];
218                }
219
220                ++p;
221        }
222
223        // End of URL?
224        if(requestLinePtr[p] == '\0')
225        {
226                // Assume HTTP 0.9
227                mHTTPVersion = HTTPVersion_0_9;
228        }
229        else
230        {
231                // Skip any more spaces
232                while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ')
233                {
234                        ++p;
235                }
236
237                // Check to see if there's the right string next...
238                if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0)
239                {
240                        // Find the version numbers
241                        int major, minor;
242                        if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2)
243                        {
244                                THROW_EXCEPTION(HTTPException, BadRequest)             
245                        }
246                       
247                        // Store version
248                        mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor;
249                }
250                else
251                {
252                        // Not good -- wrong string found
253                        THROW_EXCEPTION(HTTPException, BadRequest)             
254                }
255        }
256       
257        BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" <<
258                mRequestURI << ", version=" << mHTTPVersion);
259       
260        // If HTTP 1.1 or greater, assume keep-alive
261        if(mHTTPVersion >= HTTPVersion_1_1)
262        {
263                mClientKeepAliveRequested = true;
264        }
265       
266        // Decode query string?
267        if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty())
268        {
269                HTTPQueryDecoder decoder(mQuery);
270                decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size());
271                decoder.Finish();
272        }
273       
274        // Now parse the headers
275        ParseHeaders(rGetLine, Timeout);
276       
277        std::string expected;
278        if (GetHeader("Expect", &expected))
279        {
280                if (expected == "100-continue")
281                {
282                        mExpectContinue = true;
283                }
284        }
285       
286        // Parse form data?
287        if(mMethod == Method_POST && mContentLength >= 0)
288        {
289                // Too long? Don't allow people to be nasty by sending lots of data
290                if(mContentLength > MAX_CONTENT_SIZE)
291                {
292                        THROW_EXCEPTION(HTTPException, POSTContentTooLong)
293                }
294       
295                // Some data in the request to follow, parsing it bit by bit
296                HTTPQueryDecoder decoder(mQuery);
297                // Don't forget any data left in the GetLine object
298                int fromBuffer = rGetLine.GetSizeOfBufferedData();
299                if(fromBuffer > mContentLength) fromBuffer = mContentLength;
300                if(fromBuffer > 0)
301                {
302                        BOX_TRACE("Decoding " << fromBuffer << " bytes of "
303                                "data from getline buffer");
304                        decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer);
305                        // And tell the getline object to ignore the data we just used
306                        rGetLine.IgnoreBufferedData(fromBuffer);
307                }
308                // Then read any more data, as required
309                int bytesToGo = mContentLength - fromBuffer;
310                while(bytesToGo > 0)
311                {
312                        char buf[4096];
313                        int toRead = sizeof(buf);
314                        if(toRead > bytesToGo) toRead = bytesToGo;
315                        IOStream &rstream(rGetLine.GetUnderlyingStream());
316                        int r = rstream.Read(buf, toRead, Timeout);
317                        if(r == 0)
318                        {
319                                // Timeout, just error
320                                THROW_EXCEPTION(HTTPException, RequestReadFailed)
321                        }
322                        decoder.DecodeChunk(buf, r);
323                        bytesToGo -= r;
324                }
325                // Finish off
326                decoder.Finish();
327        }
328        else if (mContentLength > 0)
329        {
330                IOStream::pos_type bytesToCopy = rGetLine.GetSizeOfBufferedData();
331                if (bytesToCopy > mContentLength)
332                {
333                        bytesToCopy = mContentLength;
334                }
335                Write(rGetLine.GetBufferedData(), bytesToCopy);
336                SetForReading();
337                mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream());
338        }
339       
340        return true;
341}
342
343void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo)
344{
345        Seek(0, SeekType_Absolute);
346       
347        CopyStreamTo(rStreamToWriteTo);
348        IOStream::pos_type bytesCopied = GetSize();
349       
350        while (bytesCopied < mContentLength)
351        {
352                char buffer[1024];
353                IOStream::pos_type bytesToCopy = sizeof(buffer);
354                if (bytesToCopy > mContentLength - bytesCopied)
355                {
356                        bytesToCopy = mContentLength - bytesCopied;
357                }
358                bytesToCopy = mpStreamToReadFrom->Read(buffer, bytesToCopy);
359                rStreamToWriteTo.Write(buffer, bytesToCopy);
360                bytesCopied += bytesToCopy;
361        }
362}
363
364// --------------------------------------------------------------------------
365//
366// Function
367//              Name:    HTTPRequest::Send(IOStream &, int)
368//              Purpose: Write the request to an IOStream using HTTP.
369//              Created: 03/01/09
370//
371// --------------------------------------------------------------------------
372bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue)
373{
374        switch (mMethod)
375        {
376        case Method_UNINITIALISED:
377                THROW_EXCEPTION(HTTPException, RequestNotInitialised); break;
378        case Method_UNKNOWN:
379                THROW_EXCEPTION(HTTPException, BadRequest); break;
380        case Method_GET:
381                rStream.Write("GET"); break;
382        case Method_HEAD:
383                rStream.Write("HEAD"); break;
384        case Method_POST:
385                rStream.Write("POST"); break;
386        case Method_PUT:
387                rStream.Write("PUT"); break;
388        }
389
390        rStream.Write(" ");
391        rStream.Write(mRequestURI.c_str());
392        rStream.Write(" ");
393
394        switch (mHTTPVersion)
395        {
396        case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break;
397        case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break;
398        case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break;
399        default:
400                THROW_EXCEPTION(HTTPException, NotImplemented);
401        }
402
403        rStream.Write("\n");
404        std::ostringstream oss;
405
406        if (mContentLength != -1)
407        {
408                oss << "Content-Length: " << mContentLength << "\n";
409        }
410
411        if (mContentType != "")
412        {
413                oss << "Content-Type: " << mContentType << "\n";
414        }
415
416        if (mHostName != "")
417        {
418                if (mHostPort != 80)
419                {
420                        oss << "Host: " << mHostName << ":" << mHostPort <<
421                                "\n";
422                }
423                else
424                {
425                        oss << "Host: " << mHostName << "\n";
426                }
427        }
428
429        if (mpCookies)
430        {
431                THROW_EXCEPTION(HTTPException, NotImplemented);
432        }
433
434        if (mClientKeepAliveRequested)
435        {
436                oss << "Connection: keep-alive\n";
437        }
438        else
439        {
440                oss << "Connection: close\n";
441        }
442
443        for (std::vector<Header>::iterator i = mExtraHeaders.begin();
444                i != mExtraHeaders.end(); i++)
445        {
446                oss << i->first << ": " << i->second << "\n";
447        }
448       
449        if (ExpectContinue)
450        {
451                oss << "Expect: 100-continue\n";
452        }
453
454        rStream.Write(oss.str().c_str());
455        rStream.Write("\n");
456
457        return true;
458}
459
460void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout,
461        IOStream* pStreamToSend, HTTPResponse& rResponse)
462{
463        IOStream::pos_type size = pStreamToSend->BytesLeftToRead();
464       
465        if (size != IOStream::SizeOfStreamUnknown)
466        {
467                mContentLength = size;
468        }
469       
470        Send(rStreamToSendTo, Timeout, true);
471       
472        rResponse.Receive(rStreamToSendTo, Timeout);
473        if (rResponse.GetResponseCode() != 100)
474        {
475                // bad response, abort now
476                return;
477        }
478       
479        pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout);
480       
481        // receive the final response
482        rResponse.Receive(rStreamToSendTo, Timeout);
483}
484
485// --------------------------------------------------------------------------
486//
487// Function
488//              Name:    HTTPRequest::ParseHeaders(IOStreamGetLine &, int)
489//              Purpose: Private. Parse the headers of the request
490//              Created: 26/3/04
491//
492// --------------------------------------------------------------------------
493void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout)
494{
495        std::string header;
496        bool haveHeader = false;
497        while(true)
498        {
499                if(rGetLine.IsEOF())
500                {
501                        // Header terminates unexpectedly
502                        THROW_EXCEPTION(HTTPException, BadRequest)             
503                }
504
505                std::string currentLine;       
506                if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout))
507                {
508                        // Timeout
509                        THROW_EXCEPTION(HTTPException, RequestReadFailed)
510                }
511               
512                // Is this a continuation of the previous line?
513                bool processHeader = haveHeader;
514                if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t'))
515                {
516                        // A continuation, don't process anything yet
517                        processHeader = false;
518                }
519                //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str());
520               
521                // Parse the header -- this will actually process the header
522                // from the previous run around the loop.
523                if(processHeader)
524                {
525                        // Find where the : is in the line
526                        const char *h = header.c_str();
527                        int p = 0;
528                        while(h[p] != '\0' && h[p] != ':')
529                        {
530                                ++p;
531                        }
532                        // Skip white space
533                        int dataStart = p + 1;
534                        while(h[dataStart] == ' ' || h[dataStart] == '\t')
535                        {
536                                ++dataStart;
537                        }
538
539                        std::string header_name(ToLowerCase(std::string(h,
540                                p)));
541                       
542                        if (header_name == "content-length")
543                        {
544                                // Decode number
545                                long len = ::strtol(h + dataStart, NULL, 10);   // returns zero in error case, this is OK
546                                if(len < 0) len = 0;
547                                // Store
548                                mContentLength = len;
549                        }
550                        else if (header_name == "content-type")
551                        {
552                                // Store rest of string as content type
553                                mContentType = h + dataStart;
554                        }
555                        else if (header_name == "host")
556                        {
557                                // Store host header
558                                mHostName = h + dataStart;
559                               
560                                // Is there a port number to split off?
561                                std::string::size_type colon = mHostName.find_first_of(':');
562                                if(colon != std::string::npos)
563                                {
564                                        // There's a port in the string... attempt to turn it into an int
565                                        mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10);
566                                       
567                                        // Truncate the string to just the hostname
568                                        mHostName = mHostName.substr(0, colon);
569                                       
570                                        BOX_TRACE("Host: header, hostname = " <<
571                                                "'" << mHostName << "', host "
572                                                "port = " << mHostPort);
573                                }
574                        }
575                        else if (header_name == "cookie")
576                        {
577                                // Parse cookies
578                                ParseCookies(header, dataStart);
579                        }
580                        else if (header_name == "connection")
581                        {
582                                // Connection header, what is required?
583                                const char *v = h + dataStart;
584                                if(::strcasecmp(v, "close") == 0)
585                                {
586                                        mClientKeepAliveRequested = false;
587                                }
588                                else if(::strcasecmp(v, "keep-alive") == 0)
589                                {
590                                        mClientKeepAliveRequested = true;
591                                }
592                                // else don't understand, just assume default for protocol version
593                        }
594                        else
595                        {
596                                mExtraHeaders.push_back(Header(header_name,
597                                        h + dataStart));
598                        }
599                       
600                        // Unset have header flag, as it's now been processed
601                        haveHeader = false;
602                }
603
604                // Store the chunk of header the for next time round
605                if(haveHeader)
606                {
607                        header += currentLine;
608                }
609                else
610                {
611                        header = currentLine;
612                        haveHeader = true;
613                }
614
615                // End of headers?
616                if(currentLine.empty())
617                {
618                        // All done!
619                        break;
620                }               
621        }
622}
623
624
625// --------------------------------------------------------------------------
626//
627// Function
628//              Name:    HTTPRequest::ParseCookies(const std::string &, int)
629//              Purpose: Parse the cookie header
630//              Created: 20/8/04
631//
632// --------------------------------------------------------------------------
633void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts)
634{
635        const char *data = rHeader.c_str() + DataStarts;
636        const char *pos = data;
637        const char *itemStart = pos;
638        std::string name;
639       
640        enum
641        {
642                s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME
643        } state = s_NAME;
644
645        do     
646        {
647                switch(state)
648                {
649                case s_NAME:
650                        {
651                                if(*pos == '=')
652                                {
653                                        // Found the name. Store
654                                        name.assign(itemStart, pos - itemStart);
655                                        // Looking at values now
656                                        state = s_VALUE;
657                                        if((*(pos + 1)) == '"')
658                                        {
659                                                // Actually it's a quoted value, skip over that
660                                                ++pos;
661                                                state = s_VALUE_QUOTED;
662                                        }
663                                        // Record starting point for this item
664                                        itemStart = pos + 1;
665                                }
666                        }
667                        break;
668               
669                case s_VALUE:
670                        {
671                                if(*pos == ';' || *pos == ',' || *pos == '\0')
672                                {
673                                        // Name ends
674                                        ENSURE_COOKIE_JAR_ALLOCATED
675                                        std::string value(itemStart, pos - itemStart);
676                                        (*mpCookies)[name] = value;
677                                        // And move to the waiting stage
678                                        state = s_FIND_NEXT_NAME;
679                                }
680                        }
681                        break;
682               
683                case s_VALUE_QUOTED:
684                        {
685                                if(*pos == '"')
686                                {
687                                        // That'll do nicely, save it
688                                        ENSURE_COOKIE_JAR_ALLOCATED
689                                        std::string value(itemStart, pos - itemStart);
690                                        (*mpCookies)[name] = value;
691                                        // And move to the waiting stage
692                                        state = s_FIND_NEXT_NAME;
693                                }
694                        }
695                        break;
696               
697                case s_FIND_NEXT_NAME:
698                        {
699                                // Skip over terminators and white space to get to the next name
700                                if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t')
701                                {
702                                        // Name starts here
703                                        itemStart = pos;
704                                        state = s_NAME;
705                                }
706                        }
707                        break;
708               
709                default:
710                        // Ooops
711                        THROW_EXCEPTION(HTTPException, Internal)
712                        break;
713                }
714        }
715        while(*(pos++) != 0);
716}
717
718
719// --------------------------------------------------------------------------
720//
721// Function
722//              Name:    HTTPRequest::GetCookie(const char *, std::string &) const
723//              Purpose: Fetch a cookie's value. If cookie not present, returns false
724//                               and string is unaltered.
725//              Created: 20/8/04
726//
727// --------------------------------------------------------------------------
728bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const
729{
730        // Got any cookies?
731        if(mpCookies == 0)
732        {
733                return false;
734        }
735       
736        // See if it's there
737        CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
738        if(v != mpCookies->end())
739        {
740                // Return the value
741                rValueOut = v->second;
742                return true;
743        }
744       
745        return false;
746}
747
748
749// --------------------------------------------------------------------------
750//
751// Function
752//              Name:    HTTPRequest::GetCookie(const char *)
753//              Purpose: Return a string for the given cookie, or the null string if the
754//                               cookie has not been recieved.
755//              Created: 22/8/04
756//
757// --------------------------------------------------------------------------
758const std::string &HTTPRequest::GetCookie(const char *CookieName) const
759{
760        static const std::string noCookie;
761
762        // Got any cookies?
763        if(mpCookies == 0)
764        {
765                return noCookie;
766        }
767       
768        // See if it's there
769        CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName)));
770        if(v != mpCookies->end())
771        {
772                // Return the value
773                return v->second;
774        }
775       
776        return noCookie;
777}
778
779
780
Note: See TracBrowser for help on using the repository browser.