Passenger: HttpStatusExtractor.h Source File

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List

# HttpStatusExtractor.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (c) 2010 Phusion
    00004  *
    00005  *  "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  Permission is hereby granted, free of charge, to any person obtaining a copy
    00008  *  of this software and associated documentation files (the "Software"), to deal
    00009  *  in the Software without restriction, including without limitation the rights
    00010  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    00011  *  copies of the Software, and to permit persons to whom the Software is
    00012  *  furnished to do so, subject to the following conditions:
    00013  *
    00014  *  The above copyright notice and this permission notice shall be included in
    00015  *  all copies or substantial portions of the Software.
    00016  *
    00017  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    00018  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    00019  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    00020  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    00021  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    00022  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    00023  *  THE SOFTWARE.
    00024  */
    00025
    00026 #include <string>
    00027 #include <cstdio>
    00028 #include <cstdlib>
    00029 #include <cstring>
    00030
    00031 namespace Passenger {
    00032
    00033 using namespace std;
    00034
    00035 /**
    00036  * Utility class for extracting the HTTP status value from an HTTP response.
    00037  *
    00038  * This class is used for generating a proper HTTP response. The response data
    00039  * that Passenger backend processes generate are like CGI responses, and do not
    00040  * include an initial "HTTP/1.1 [status here]" line, so this class used to
    00041  * extract the status from the response in order to generate a proper initial
    00042  * HTTP response line.
    00043  *
    00044  * This class is supposed to be used as follows:
    00045  * - Keep feeding HTTP response data until feed() returns true. feed()
    00046  *   buffers all fed data until it is able to extract the HTTP status.
    00047  * - Call getStatusLine() to retrieve the status line, and use this to generate
    00048  *   an HTTP response line.
    00049  * - Call getBuffer() to retrieve all fed data so far. This data can be sent to
    00050  *   the HTTP client.
    00051  *
    00052  * This class will also ensure that the status line contains a status text, e.g.
    00053  * if the HTTP data's status value is only "200" then "OK" will be automatically
    00054  * appended.
    00055  *
    00056  * @note
    00057  * When the API documentation for this class refers to "\r\n", we actually
    00058  * mean "\x0D\x0A" (the HTTP line termination string). "\r\n" is only written
    00059  * out of convenience.
    00060  */
    00061 class HttpStatusExtractor {
    00062 private:
    00063         static const char CR = '\x0D';
    00064         static const char LF = '\x0A';
    00065
    00066         string buffer;
    00067         unsigned int searchStart;
    00068         bool fullHeaderReceived;
    00069         string statusLine;
    00070
    00071         bool extractStatusLine() {
    00072                 static const char statusHeaderName[] = "Status: ";
    00073                 string::size_type start_pos, newline_pos;
    00074
    00075                 if (buffer.size() > sizeof(statusHeaderName) - 1
    00076                  && memcmp(buffer.c_str(), statusHeaderName, sizeof(statusHeaderName) - 1) == 0) {
    00077                         // Status line starts at beginning of the header.
    00078                         start_pos = sizeof(statusHeaderName) - 1;
    00079                         newline_pos = buffer.find("\x0D\x0A", 0, 2) + 2;
    00080                 } else {
    00081                         // Status line is not at the beginning of the header.
    00082                         // Look for it.
    00083                         start_pos = buffer.find("\x0D\x0AStatus: ");
    00084                         if (start_pos != string::npos) {
    00085                                 start_pos += 2 + sizeof(statusHeaderName) - 1;
    00086                                 newline_pos = buffer.find("\x0D\x0A", start_pos, 2) + 2;
    00087                         }
    00088                 }
    00089                 if (start_pos != string::npos) {
    00090                         // Status line has been found. Extract it.
    00091                         statusLine = buffer.substr(start_pos, newline_pos - start_pos);
    00092                         addStatusTextIfNecessary();
    00093                         return true;
    00094                 } else {
    00095                         // Status line is not found. Do not change default
    00096                         // status line value.
    00097                         return false;
    00098                 }
    00099         }
    00100
    00101         void addStatusTextIfNecessary() {
    00102                 if (statusLine.find(' ') == string::npos) {
    00103                         // The status line doesn't contain a status text, so add it.
    00104                         int statusCode = atoi(statusLine.c_str());
    00105                         switch (statusCode) {
    00106                         case 100:
    00107                                 statusLine = "100 Continue\x0D\x0A";
    00108                                 break;
    00109                         case 101:
    00110                                 statusLine = "101 Switching Protocols\x0D\x0A";
    00111                                 break;
    00112                         case 102:
    00113                                 statusLine = "102 Processing\x0D\x0A";
    00114                                 break;
    00115                         case 200:
    00116                                 statusLine = "200 OK\x0D\x0A";
    00117                                 break;
    00118                         case 201:
    00119                                 statusLine = "201 Created\x0D\x0A";
    00120                                 break;
    00121                         case 202:
    00122                                 statusLine = "202 Accepted\x0D\x0A";
    00123                                 break;
    00124                         case 203:
    00125                                 statusLine = "203 Non-Authoritative Information\x0D\x0A";
    00126                                 break;
    00127                         case 204:
    00128                                 statusLine = "204 No Content\x0D\x0A";
    00129                                 break;
    00130                         case 205:
    00131                                 statusLine = "205 Reset Content\x0D\x0A";
    00132                                 break;
    00133                         case 206:
    00134                                 statusLine = "206 Partial Content\x0D\x0A";
    00135                                 break;
    00136                         case 207:
    00137                                 statusLine = "207 Multi-Status\x0D\x0A";
    00138                                 break;
    00139                         case 300:
    00140                                 statusLine = "300 Multiple Choices\x0D\x0A";
    00141                                 break;
    00142                         case 301:
    00143                                 statusLine = "301 Moved Permanently\x0D\x0A";
    00144                                 break;
    00145                         case 302:
    00146                                 statusLine = "302 Found\x0D\x0A";
    00147                                 break;
    00148                         case 303:
    00149                                 statusLine = "303 See Other\x0D\x0A";
    00150                                 break;
    00151                         case 304:
    00152                                 statusLine = "304 Not Modified\x0D\x0A";
    00153                                 break;
    00154                         case 305:
    00155                                 statusLine = "305 Use Proxy\x0D\x0A";
    00156                                 break;
    00157                         case 306:
    00158                                 statusLine = "306 Switch Proxy\x0D\x0A";
    00159                                 break;
    00160                         case 307:
    00161                                 statusLine = "307 Temporary Redirect\x0D\x0A";
    00162                                 break;
    00163                         case 308:
    00164                                 // Google Gears: http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal
    00165                                 statusLine = "308 Resume Incomplete\x0D\x0A";
    00166                                 break;
    00167                         case 400:
    00168                                 statusLine = "400 Bad Request\x0D\x0A";
    00169                                 break;
    00170                         case 401:
    00171                                 statusLine = "401 Unauthorized\x0D\x0A";
    00172                                 break;
    00173                         case 402:
    00174                                 statusLine = "402 Payment Required\x0D\x0A";
    00175                                 break;
    00176                         case 403:
    00177                                 statusLine = "403 Forbidden\x0D\x0A";
    00178                                 break;
    00179                         case 404:
    00180                                 statusLine = "404 Not Found\x0D\x0A";
    00181                                 break;
    00182                         case 405:
    00183                                 statusLine = "405 Method Not Allowed\x0D\x0A";
    00184                                 break;
    00185                         case 406:
    00186                                 statusLine = "406 Not Acceptable\x0D\x0A";
    00187                                 break;
    00188                         case 407:
    00189                                 statusLine = "407 Proxy Authentication Required\x0D\x0A";
    00190                                 break;
    00191                         case 408:
    00192                                 statusLine = "408 Request Timeout\x0D\x0A";
    00193                                 break;
    00194                         case 409:
    00195                                 statusLine = "409 Conflict\x0D\x0A";
    00196                                 break;
    00197                         case 410:
    00198                                 statusLine = "410 Gone\x0D\x0A";
    00199                                 break;
    00200                         case 411:
    00201                                 statusLine = "411 Length Required\x0D\x0A";
    00202                                 break;
    00203                         case 412:
    00204                                 statusLine = "412 Precondition Failed\x0D\x0A";
    00205                                 break;
    00206                         case 413:
    00207                                 statusLine = "413 Request Entity Too Large\x0D\x0A";
    00208                                 break;
    00209                         case 414:
    00210                                 statusLine = "414 Request-URI Too Long\x0D\x0A";
    00211                                 break;
    00212                         case 415:
    00213                                 statusLine = "415 Unsupported Media Type\x0D\x0A";
    00214                                 break;
    00215                         case 416:
    00216                                 statusLine = "416 Requested Range Not Satisfiable\x0D\x0A";
    00217                                 break;
    00218                         case 417:
    00219                                 statusLine = "417 Expectation Failed\x0D\x0A";
    00220                                 break;
    00221                         case 418:
    00222                                 statusLine = "418 Not A Funny April Fools Joke\x0D\x0A";
    00223                                 break;
    00224                         case 422:
    00225                                 statusLine = "422 Unprocessable Entity\x0D\x0A";
    00226                                 break;
    00227                         case 423:
    00228                                 statusLine = "423 Locked\x0D\x0A";
    00229                                 break;
    00230                         case 424:
    00231                                 statusLine = "424 Unordered Collection\x0D\x0A";
    00232                                 break;
    00233                         case 426:
    00234                                 statusLine = "426 Upgrade Required\x0D\x0A";
    00235                                 break;
    00236                         case 449:
    00237                                 statusLine = "449 Retry With\x0D\x0A";
    00238                                 break;
    00239                         case 450:
    00240                                 statusLine = "450 Blocked\x0D\x0A";
    00241                                 break;
    00242                         case 500:
    00243                                 statusLine = "500 Internal Server Error\x0D\x0A";
    00244                                 break;
    00245                         case 501:
    00246                                 statusLine = "501 Not Implemented\x0D\x0A";
    00247                                 break;
    00248                         case 502:
    00249                                 statusLine = "502 Bad Gateway\x0D\x0A";
    00250                                 break;
    00251                         case 503:
    00252                                 statusLine = "503 Service Unavailable\x0D\x0A";
    00253                                 break;
    00254                         case 504:
    00255                                 statusLine = "504 Gateway Timeout\x0D\x0A";
    00256                                 break;
    00257                         case 505:
    00258                                 statusLine = "505 HTTP Version Not Supported\x0D\x0A";
    00259                                 break;
    00260                         case 506:
    00261                                 statusLine = "506 Variant Also Negotiates\x0D\x0A";
    00262                                 break;
    00263                         case 507:
    00264                                 statusLine = "507 Insufficient Storage\x0D\x0A";
    00265                                 break;
    00266                         case 509:
    00267                                 statusLine = "509 Bandwidth Limit Exceeded\x0D\x0A";
    00268                                 break;
    00269                         case 510:
    00270                                 statusLine = "510 Not Extended\x0D\x0A";
    00271                                 break;
    00272                         default:
    00273                                 char temp[32];
    00274                                 snprintf(temp, sizeof(temp),
    00275                                         "%d Unknown Status Code\x0D\x0A",
    00276                                         statusCode);
    00277                                 temp[sizeof(temp) - 1] = '\0';
    00278                                 statusLine = temp;
    00279                         }
    00280                 }
    00281         }
    00282
    00283 public:
    00284         HttpStatusExtractor() {
    00285                 searchStart = 0;
    00286                 fullHeaderReceived = false;
    00287                 statusLine = "200 OK\r\n";
    00288         }
    00289
    00290         /**
    00291          * Feed HTTP response data to this HttpStatusExtractor.
    00292          *
    00293          * One is to keep feeding data until this method returns true.
    00294          * When a sufficient amount of data has been fed, this method will
    00295          * extract the status line from the data that has been fed so far,
    00296          * and return true.
    00297          *
    00298          * Do not call this method again once it has returned true.
    00299          *
    00300          * It is safe to feed excess data. That is, it is safe if the 'data'
    00301          * argument contains a part of the HTTP response body.
    00302          * HttpStatusExtractor will only look for the status line in the HTTP
    00303          * response header, not in the HTTP response body. All fed data is
    00304          * buffered and will be available via getBuffer(), so no data will be
    00305          * lost.
    00306          *
    00307          * @return Whether the HTTP status has been extracted yet.
    00308          * @pre feed() did not previously return true.
    00309          * @pre data != NULL
    00310          * @pre size > 0
    00311          */
    00312         bool feed(const char *data, unsigned int size) {
    00313                 if (fullHeaderReceived) {
    00314                         return true;
    00315                 }
    00316                 buffer.append(data, size);
    00317                 for (; buffer.size() >= 3 && searchStart < buffer.size() - 3; searchStart++) {
    00318                         if (buffer[searchStart] == CR &&
    00319                             buffer[searchStart + 1] == LF &&
    00320                             buffer[searchStart + 2] == CR &&
    00321                             buffer[searchStart + 3] == LF) {
    00322                                 fullHeaderReceived = true;
    00323                                 extractStatusLine();
    00324                                 return true;
    00325                         }
    00326                 }
    00327                 return false;
    00328         }
    00329
    00330         /**
    00331          * Returns the HTTP status line that has been determined.
    00332          *
    00333          * The default value is "200 OK\r\n", which is returned if the HTTP
    00334          * response data that has been fed so far does not include a status
    00335          * line.
    00336          *
    00337          * @note The return value includes a trailing CRLF, e.g. "404 Not Found\r\n".
    00338          */
    00339         string getStatusLine() const {
    00340                 return statusLine;
    00341         }
    00342
    00343         /**
    00344          * Get the data that has been fed so far.
    00345          */
    00346         string getBuffer() const {
    00347                 return buffer;
    00348         }
    00349 };
    00350
    00351 } // namespace Passenger