| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "ink_platform.h" |
| #include "ink_defs.h" |
| #include "ink_string.h" |
| #include "ink_time.h" |
| |
| #include "WebUtils.h" |
| #include "WebHttpMessage.h" |
| #include "TextBuffer.h" |
| #include "MIME.h" |
| #include "I_Version.h" |
| #include "Main.h" |
| |
| /**************************************************************************** |
| * |
| * WebHttpMessage.cc - classes to store information about incoming requests |
| * and create hdrs for outgoing requests |
| * |
| * |
| * |
| ****************************************************************************/ |
| |
| const char *const httpStatStr[] = { |
| "100 Continue\r\n", |
| "101 Switching Protocols\r\n", |
| "200 OK\r\n", |
| "201 Created\r\n", |
| "202 Accepted\r\n", |
| "203 Non-Authorative Information\r\n", |
| "204 No Content\r\n", |
| "205 Reset Content\r\n", |
| "206 Partial Content\r\n", |
| "300 Multiple Choices\r\n", |
| "301 Moved Permanently\r\n", |
| "302 Moved Temporarily\r\n", |
| "303 See Other\r\n", |
| "304 Not Modified\r\n", |
| "305 Use Proxy\r\n", |
| "400 Bad Request\r\n", |
| "401 Unauthorized\r\n", |
| "402 Payment Required\r\n", |
| "403 Forbidden\r\n", |
| "404 Not Found\r\n", |
| "500 Internal Server Error\r\n", |
| "501 Not Implemented\r\n", |
| "502 Bad Gateway\r\n", |
| "503 Service Unvailable\r\n", |
| "504 Gateway Timeout\r\n", |
| "505 HTTP Version Not Supported\r\n" |
| }; |
| |
| const char *const httpStatCode[] = { |
| "100", |
| "101", |
| "200", |
| "201", |
| "202", |
| "203", |
| "204", |
| "205", |
| "206", |
| "300", |
| "301", |
| "302", |
| "303", |
| "304", |
| "305", |
| "400", |
| "401", |
| "402", |
| "403", |
| "404", |
| "500", |
| "501", |
| "502", |
| "503", |
| "504", |
| "505" |
| }; |
| |
| const char *const contentTypeStr[] = { |
| "text/plain", |
| "text/html", |
| "text/css", |
| "text/unknown", |
| "image/gif", |
| "image/jpeg", |
| "image/png", |
| "application/java-vm", |
| "application/x-javascript", |
| "application/x-x509-ca-cert", |
| "application/x-ns-proxy-autoconfig", |
| "application/zip" |
| }; |
| |
| |
| // httpMessage::httpMessage() |
| // |
| httpMessage::httpMessage() |
| { |
| |
| method = METHOD_NONE; |
| header = NULL; |
| body = NULL; |
| scheme = SCHEME_NONE; |
| file = NULL; |
| query = NULL; |
| conLen = -1; |
| referer = NULL; |
| conType_str = NULL; |
| authMessage = NULL; |
| parser = new Tokenizer(" \t\n\r"); |
| modificationTime = -1; |
| modContentLength = -1; |
| client_request = NULL; |
| } |
| |
| void |
| httpMessage::getLogInfo(const char **request) |
| { |
| *request = client_request; |
| } |
| |
| // returns zero if everything was OK or non-zero if |
| // the request was malformed |
| int |
| httpMessage::addRequestLine(char *request) |
| { |
| |
| const char *method_str; |
| const char *scheme_str; |
| const char *URI; |
| char *tmp; |
| int requestLen; |
| |
| // Make a copy of the string so that we |
| // log it later |
| client_request = ats_strdup(request); |
| requestLen = strlen(client_request); |
| if (requestLen > 0 && client_request[requestLen - 1] == '\r') { |
| client_request[requestLen - 1] = '\0'; |
| } |
| |
| parser->Initialize(request, SHARE_TOKS); |
| method_str = (*parser)[0]; |
| URI = (*parser)[1]; |
| scheme_str = (*parser)[2]; |
| |
| // Check for an empty request |
| if (method_str == NULL) { |
| return 1; |
| } |
| // Determine Method |
| if (strcmp(method_str, "GET") == 0) { |
| method = METHOD_GET; |
| } else if (strcmp(method_str, "POST") == 0) { |
| method = METHOD_POST; |
| } else if (strcmp(method_str, "HEAD") == 0) { |
| method = METHOD_HEAD; |
| } else { |
| method = METHOD_NONE; |
| } |
| |
| if (URI == NULL) { |
| return 1; |
| } |
| // Get the scheme |
| // |
| // We only understand HTTP/1.0 |
| // |
| // If a browser asks for HTTP, we send back 1.0 |
| // If there is no scheme, assume HTTP |
| // If there is another scheme, mark it unknown |
| // |
| if (scheme_str == NULL) { |
| scheme = SCHEME_NONE; |
| } else { |
| |
| if (strncasecmp(scheme_str, "HTTP", 4) == 0) { |
| scheme = SCHEME_HTTP; |
| } else if (strncasecmp(scheme_str, "SHTTP", 5) == 0) { |
| scheme = SCHEME_SHTTP; |
| } else { |
| scheme = SCHEME_UNKNOWN; |
| } |
| } |
| |
| // Now sort out the file verses query portion of the |
| // the request |
| // |
| // First check to see if the client sent us a full URL |
| if (strncmp("http://", URI, 7) == 0) { |
| URI = strstr(URI + 7, "/"); |
| if (URI == NULL) { |
| return 1; |
| } |
| } |
| // Now allocate space for the document path portion |
| // of the URI along with the query if any |
| unsigned int amtToAllocate = strlen(URI) + 1; |
| file = new char[amtToAllocate]; |
| ink_strlcpy(file, URI, amtToAllocate + 1); |
| tmp = strstr(file, "?"); |
| if (tmp != NULL) { |
| // There is a form submission |
| *tmp = '\0'; |
| query = tmp + 1; |
| // Remove trailing "\" |
| query[strlen(query)] = '\0'; |
| } |
| |
| return 0; |
| } |
| |
| void |
| httpMessage::addHeader(char *hdr) |
| { |
| |
| const char *authType; |
| const char *auth; |
| int len; |
| const char *hdrName; |
| const char *hdrArg1; |
| |
| parser->Initialize(hdr, SHARE_TOKS); |
| hdrName = (*parser)[0]; |
| hdrArg1 = (*parser)[1]; |
| |
| // All headers require at least tokens |
| if (hdrName == NULL || hdrArg1 == NULL) { |
| return; |
| } |
| |
| if (strncasecmp("Content-length:", hdrName, 15) == 0) { |
| conLen = atoi(hdrArg1); |
| } else if (strncasecmp("Referer:", hdrName, 8) == 0) { |
| unsigned int amtToAllocate = strlen(hdrArg1); |
| referer = new char[amtToAllocate + 1]; |
| ink_strlcpy(referer, hdrArg1, amtToAllocate + 1); |
| } else if (strncasecmp("Content-type:", hdrName, 13) == 0) { |
| unsigned int amtToAllocate = strlen(hdrArg1); |
| conType_str = new char[amtToAllocate + 1]; |
| ink_strlcpy(conType_str, hdrArg1, amtToAllocate + 1); |
| } else if (strncasecmp("Authorization:", hdrName, 14) == 0) { |
| authType = hdrArg1; |
| if (strcmp(authType, "Basic") == 0) { |
| auth = (*parser)[2]; |
| len = strlen(auth) + 1; |
| authMessage = new char[len]; |
| ink_strlcpy(authMessage, auth, len); |
| } |
| } else if (strncasecmp("If-Modified-Since:", hdrName, 18) == 0) { |
| // Disabled due to thread safety issues |
| getModDate(); |
| } |
| } |
| |
| |
| // httpMessage::getModDate() |
| // |
| // A function to extract info from the If-Modified-Since http |
| // header. |
| // |
| // Currently brokens since both strptime and mktime are not thread |
| // safe |
| // |
| void |
| httpMessage::getModDate() |
| { |
| |
| // Since the dates have spaces in them, we have to recontruct |
| // them. Sigh. |
| int i = 1; |
| int numDateFields; |
| int dateSize = 0; |
| char *dateStr; |
| const char *clStr; |
| int tmpLen; |
| Tokenizer *equalTok = NULL; |
| |
| // First, figure out the number of fields |
| for (i = 1; (*parser)[i] != NULL && strstr((*parser)[i++], ";") == NULL;); |
| numDateFields = i - 1; |
| |
| if (numDateFields > 0) { |
| |
| // Next, figure out the size of the string we need |
| for (i = 0; i < numDateFields; i++) { |
| dateSize += strlen((*parser)[i + 1]); |
| } |
| dateStr = new char[dateSize + 1 + numDateFields]; |
| *dateStr = '\0'; |
| |
| // Rebuild the date string from the parsed |
| // stuff |
| for (i = 0; i < numDateFields; i++) { |
| ink_strlcat(dateStr, (*parser)[i + 1], dateSize + 1); |
| tmpLen = strlen(dateStr); |
| dateStr[tmpLen] = ' '; |
| dateStr[tmpLen + 1] = '\0'; |
| } |
| |
| // There could be junk of the end of array like a ; |
| tmpLen = strlen(dateStr); |
| while (!isalpha(dateStr[tmpLen])) { |
| dateStr[tmpLen] = '\0'; |
| tmpLen--; |
| } |
| |
| modificationTime = mime_parse_date(dateStr); |
| delete[]dateStr; |
| |
| // Now figure out the content length from if modified |
| if (parser->getNumber() > numDateFields + 1) { |
| clStr = (*parser)[numDateFields + 1]; |
| equalTok = new Tokenizer("=\r\n"); |
| equalTok->Initialize(clStr); |
| if (strcasecmp("length", (*equalTok)[0]) == 0) { |
| modContentLength = atoi((*equalTok)[1]); |
| } |
| delete equalTok; |
| } |
| } |
| } |
| |
| // httpMessage::addRequestBody(int fd) |
| // |
| // Read the request body of of the socket and make a local |
| // copy of the entire thing. Return zero if all went ok |
| // or return -1 with there was an error |
| int |
| httpMessage::addRequestBody(SocketInfo socketD) |
| { |
| char *nextRead; |
| int bytesRead = 0; |
| int readResult; |
| |
| if (conLen < 0) { |
| return 0; |
| } |
| |
| body = new char[conLen + 1]; |
| nextRead = body; |
| |
| while (bytesRead < conLen) { |
| readResult = socket_read(socketD, nextRead, conLen - bytesRead); |
| |
| if (readResult <= 0) { |
| // There was an error on the read. |
| *nextRead = '\0'; |
| return -1; |
| } else { |
| bytesRead += readResult; |
| nextRead += readResult; |
| } |
| } |
| *nextRead = '\0'; |
| return 0; |
| } |
| |
| httpMessage::~httpMessage() |
| { |
| delete[]body; |
| delete[]file; |
| delete[]referer; |
| delete[]conType_str; |
| delete[]authMessage; |
| |
| ats_free(client_request); |
| delete parser; |
| } |
| |
| /* 01/14/99 elam - |
| * Commented out because g++ is not happy with iostream.h |
| void httpMessage::Print() { |
| cout << "Method: " << method << endl; |
| cout << "File: " << file << endl; |
| cout << "Query: " << query << endl; |
| cout << "Scheme: " << scheme << endl; |
| cout << "Content Length: " << conLen << endl; |
| cout << "First Header: " << header << endl; |
| cout << "Message Body: " << body << endl << endl; |
| } |
| */ |
| |
| httpResponse::httpResponse() |
| { |
| // Default is no refresh header; |
| refresh = -1; |
| conLen = -1; |
| conType = TEXT_HTML; |
| explicitConType = NULL; |
| refreshURL = NULL; |
| lastMod = -1; |
| authRealm = NULL; |
| dateResponse = NULL; |
| status = STATUS_INTERNAL_SERVER_ERROR; |
| locationURL = NULL; |
| cachable = 1; |
| } |
| |
| httpResponse::~httpResponse() |
| { |
| ats_free(explicitConType); |
| ats_free(authRealm); |
| ats_free(refreshURL); |
| ats_free(locationURL); |
| ats_free(dateResponse); |
| } |
| |
| int |
| httpResponse::writeHdr(SocketInfo socketD) |
| { |
| const char versionStr[] = "HTTP/1.0 "; |
| const char serverStr[] = "Server: "; |
| const char managerStr[] = "Traffic Manager "; |
| const char noStoreStr[] = "Cache-Control: no-store\r\n"; |
| const char noCacheStr[] = "Pragma: no-cache\r\n"; |
| const char lenStr[] = "Content-length: "; |
| const char refreshStr[] = "Refresh: "; |
| const char authStr[] = "WWW-Authenticate: Basic realm=\""; |
| const char dateStr[] = "Date: "; |
| const char lastModStr[] = "Last-modified: "; |
| const char locationStr[] = "Location: "; |
| const char refreshURLStr[] = "; URL="; |
| time_t currentTime; |
| const int bufSize = 512; |
| char buffer[bufSize]; |
| char *reply; |
| int bytesWritten; |
| textBuffer hdr(4096); |
| |
| hdr.copyFrom(versionStr, strlen(versionStr)); |
| |
| // Record the status |
| hdr.copyFrom(httpStatStr[status], strlen(httpStatStr[status])); |
| |
| // Record Server Name |
| hdr.copyFrom(serverStr, strlen(serverStr)); |
| hdr.copyFrom(managerStr, strlen(managerStr)); |
| hdr.copyFrom(appVersionInfo.VersionStr, strlen(appVersionInfo.VersionStr)); |
| hdr.copyFrom("\r\n", 2); |
| |
| // Record refresh |
| if (refresh >= 0) { |
| hdr.copyFrom(refreshStr, strlen(refreshStr)); |
| snprintf(buffer, bufSize - 1, "%d", refresh); |
| hdr.copyFrom(buffer, strlen(buffer)); |
| if (refreshURL != NULL) { |
| hdr.copyFrom(refreshURLStr, strlen(refreshURLStr)); |
| hdr.copyFrom(refreshURL, strlen(refreshURL)); |
| } |
| hdr.copyFrom("\r\n", 2); |
| } |
| |
| // Location Header |
| if (locationURL != NULL) { |
| hdr.copyFrom(locationStr, strlen(locationStr)); |
| hdr.copyFrom(locationURL, strlen(locationURL)); |
| hdr.copyFrom("\r\n", 2); |
| } |
| |
| // Always send the current time |
| currentTime = time(NULL); |
| mime_format_date(buffer, currentTime); |
| // We were able to genarate the date string |
| hdr.copyFrom(dateStr, strlen(dateStr)); |
| hdr.copyFrom(buffer, strlen(buffer)); |
| hdr.copyFrom("\r\n", 2); |
| dateResponse = ats_strdup(buffer); |
| |
| // Not cachable if marked not cachable, or has no L-M date |
| if ((getCachable() == 0) || (lastMod == -1)) { |
| // Use "Control-Control: no-store" for HTTP 1.1 |
| // compliant browsers. |
| hdr.copyFrom(noStoreStr, strlen(noStoreStr)); |
| // For non-1.1 compliant browsers, we'll set |
| // "Pragma: no-cache" just to be safe |
| hdr.copyFrom(noCacheStr, strlen(noCacheStr)); |
| } else if (lastMod != -1) { |
| // Send the last modified time if we have it |
| mime_format_date(buffer, lastMod); |
| // We were able to genarate the date string |
| hdr.copyFrom(lastModStr, strlen(lastModStr)); |
| hdr.copyFrom(buffer, strlen(buffer)); |
| hdr.copyFrom("\r\n", 2); |
| } |
| |
| snprintf(buffer, sizeof(buffer), "Content-type: %s\r\n", contentTypeStr[conType]); |
| hdr.copyFrom(buffer, strlen(buffer)); |
| |
| // Issue an authentication challenge if we are |
| // authorized |
| if (status == STATUS_UNAUTHORIZED) { |
| hdr.copyFrom(authStr, strlen(authStr)); |
| hdr.copyFrom(authRealm, strlen(authRealm)); |
| hdr.copyFrom("\"\r\n", 3); |
| } |
| |
| if (conLen >= 0) { |
| hdr.copyFrom(lenStr, strlen(lenStr)); |
| snprintf(buffer, sizeof(buffer), "%d", conLen); |
| hdr.copyFrom(buffer, strlen(buffer)); |
| hdr.copyFrom("\r\n", 2); |
| } |
| // End of Header marked by empty line |
| hdr.copyFrom("\r\n", 2); |
| |
| reply = hdr.bufPtr(); |
| bytesWritten = socket_write(socketD, reply, strlen(reply)); |
| return bytesWritten; |
| } |
| |
| |
| void |
| httpResponse::setContentType(const char *str) |
| { |
| if (str != NULL) { |
| explicitConType = ats_strdup(str); |
| } |
| } |
| |
| void |
| httpResponse::setRealm(const char *realm) |
| { |
| if (realm != NULL) { |
| authRealm = ats_strdup(realm); |
| } |
| } |
| |
| void |
| httpResponse::setRefreshURL(const char *url) |
| { |
| if (url != NULL) { |
| refreshURL = ats_strdup(url); |
| } |
| } |
| |
| void |
| httpResponse::setLocationURL(const char *url) |
| { |
| if (url != NULL) { |
| locationURL = ats_strdup(url); |
| } |
| } |
| |
| |
| void |
| httpResponse::getLogInfo(const char **date, HttpStatus_t * statusIn, int *length) |
| { |
| *date = dateResponse; |
| *statusIn = status; |
| *length = conLen; |
| } |