//%LICENSE//////////////////////////////////////////////////////////////// // // Licensed to The Open Group (TOG) under one or more contributor license // agreements. Refer to the OpenPegasusNOTICE.txt file distributed with // this work for additional information regarding copyright ownership. // Each contributor licenses this file to you under the OpenPegasus Open // Source License; you may not use this file except in compliance with the // License. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ////////////////////////////////////////////////////////////////////////// // //%///////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include PEGASUS_USING_STD; PEGASUS_NAMESPACE_BEGIN const Uint32 WebProcessor::DEFAULT_RESPONSE_BUFFER_SIZE = 8192;// 8 KB const Uint32 WebProcessor::MAX_URI_LENGTH = 256; const String WebProcessor::HTTP_VERSION = "1.1"; const String WebProcessor::GZIP = "gzip"; const String WebProcessor::DEFLATE = "deflate"; WebProcessor::WebProcessor(WebServer* const webServer) : _webConfig(), _webServer(webServer) { } WebProcessor::~WebProcessor() { } void WebProcessor::handleWebRequest(WebRequest* request) { PEG_METHOD_ENTER(TRC_WEBSERVER, "WebProcessor::handleWebRequest(WebRequest* request)"); if (!request) { PEG_METHOD_EXIT(); return; } Uint32 queueId = request->getQueueId(); // check protocol version if (String::equal(request->httpVersion, "") || PEG_NOT_FOUND == request->httpVersion.find("HTTP/")) {// bad request _sendError( HTTP_STATUSCODE_BADREQUEST, queueId, "The request has a bad syntax, http version string not found!"); PEG_METHOD_EXIT(); return; } else if (!String::equal(request->httpVersion, "HTTP/" + HTTP_VERSION)) {// protocol-version not supported _sendError( HTTP_STATUSCODE_VERSIONNOTSUPPORTED, queueId, "The requested HTTP version '" + request->httpVersion + "' is not supported by this server!"); PEG_METHOD_EXIT(); return; } PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - httpVersion='%s'", (const char*)request->httpVersion.getCString())); String* method; // check HTTPmethod if (request->httpMethod == HTTP_METHOD_GET) { method = new String("GET"); } else if (request->httpMethod == HTTP_METHOD_HEAD) { method = new String("HEAD"); } else {// handle bad request (method not allowed) _sendError( HTTP_STATUSCODE_METHODNOTALLOWED, queueId, "The requested HTTP method is not supported by this server," " 'GET' and 'HEAD' only!", "Allow: HEAD, GET"); PEG_METHOD_EXIT(); return; } PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - httpMethod='%s'", (const char*)method->getCString())); // get absolute filename from URI String fileName; Uint32 statusCode = _getFileNameForURI(request->requestURI, fileName); if (statusCode != HTTP_STATUSCODE_OK) { _sendError( statusCode, queueId, "

Request-URI: '" + request->requestURI + "'
" + "Current web-root: '" + _webConfig.getWebRoot() + "'" + ((statusCode == HTTP_STATUSCODE_FORBIDDEN)? ("

Reason: It points to a directory or " "the requested file's real path is not located " "in the webRoot!

") :("

"))); PEG_METHOD_EXIT(); return; } PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - URI maps to valid/allowed " "fileName='%s'", (const char*)fileName.getCString())); // get and check contentType for requested file String contentType; /* RFC 2616, section 10.4.7 '406 Not Acceptable' * * Note: HTTP/1.1 servers are allowed to return responses which are * not acceptable according to the accept headers sent in the * request. In some cases, this may even be preferable to sending a * 406 response. User agents are encouraged to inspect the headers of * an incoming response to determine if it is acceptable. * * => So there's no need to check whether the client is willing to accept * the delivered file with a header line in the response including a * mime-type definition it does not know. */ if (!_getContentType(fileName, contentType)) { // no contentType known for requested file(-extension) _sendError( HTTP_STATUSCODE_FORBIDDEN, queueId, "The requested file '" + fileName + "' has an undefined content type. It will not be served!"); PEG_METHOD_EXIT(); return; } PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - response contentType='%s'", (const char*)contentType.getCString())); /* is it a text based mime-type ? * * Notice: * The mimeTypes-file mapping could be extended to an additional value * indicating whether the file is to treat as binary or text. * There are text based mime-types which do not start with 'text/'. */ Boolean isBinFile = String::compare(contentType.subString(0, 4), "text"); PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - contentType is binary='%s'", ((isBinFile)?"true":"false"))); /* * check requested encodings * (for ex. 'gzip, defalte') */ String enc;// determined encoding to use statusCode = _getRequestHeaderValue( request->encodings, const_cast(GZIP), enc); if (statusCode != HTTP_STATUSCODE_OK) { statusCode = _getRequestHeaderValue( request->encodings, const_cast(DEFLATE), enc); if (statusCode == HTTP_STATUSCODE_BADREQUEST) { _sendError( statusCode, queueId, "Bad Syntax in header-parameter 'Accept-Encoding' !"); PEG_METHOD_EXIT(); return; } } //compress files bigger than 10KB, only Boolean compressionFlag = String::equal(GZIP, enc) || String::equal(DEFLATE, enc); PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL3,"WebServer::handleWebRequest" "(WebRequest* webRequest) - response encoding='%s', " "compressionFlag='%s'", (const char*)enc.getCString(), (compressionFlag)?"true":"false")); PEG_TRACE_CSTRING(TRC_WEBSERVER,Tracer::LEVEL2,"WebServer::handleWebRequest" "(WebRequest* webRequest) - All Headers have been parsed and " "successfully validated."); /* * check access to file/if exists */ if (!FileSystem::canRead(fileName)) {// file not accessible _sendError( HTTP_STATUSCODE_FORBIDDEN, queueId, "The requested file '" + fileName + "' is not accessible!"); PEG_METHOD_EXIT(); return; } PEG_TRACE((TRC_WEBSERVER, Tracer::LEVEL4, "WebServer::handleWebRequest(WebRequest* webRequest) - " "File accessible, creating response. HTTP-statusCode: %d ", statusCode)); // initialize response-buffer Buffer tmp = Buffer(DEFAULT_RESPONSE_BUFFER_SIZE); Buffer& _message = tmp; /* * create response */ _message << "HTTP/" << HTTP_VERSION << " " << statusCode << " " << _getStatusMessage(statusCode).getCString() << "\r\n"; // response Date _message << "Date: " << _getCurrentDate() << "\r\n"; _message << "Last-Modified: " << _getDateOfLastMod(fileName) << "\r\n"; if (isBinFile)// || compressionFlag) TODO: enable for compression { _message << "Accept-Ranges: bytes\r\n"; } else { _message << "Accept-Ranges: text/plain\r\n"; } // set content-type of file to deliver _message << "Content-Type: " << contentType.getCString(); if (!isBinFile && !compressionFlag) {// no charset for bin-files _message << ";charset=utf-8"; } _message << "\r\n"; // compress the file before delivery ? if (compressionFlag) { /*TODO * compress the file and set the new filename * for further processing */ // _message << "Content-Encoding: " << enc << "\r\n"; } Uint32 fileSize; if (!FileSystem::getFileSize(fileName, fileSize)) { _sendError( HTTP_STATUSCODE_INTERNALSERVERERROR, queueId, "The size for requested file '" + fileName + "' could not be determined!"); PEG_METHOD_EXIT(); return; } // get file's size if (isBinFile)// || compressionFlag) TODO: enable for compression {/* * plain text-files cause trouble due to the "\r\n" which is not counted, * but added for each line */ _message << "Content-Length: " << fileSize << "\r\n"; } _message << "Connection: keep-alive \r\n"; // end of headers _message << "\r\n"; PEG_TRACE_CSTRING(TRC_WEBSERVER,Tracer::LEVEL2,"WebServer::handleWebRequest" "(WebRequest* webRequest) - All response headers have been " "written."); /* * Write Body */ if (request->httpMethod != HTTP_METHOD_HEAD) { if (fileSize != PEG_NOT_FOUND) { _message.reserveCapacity(fileSize); } if (isBinFile)// || compressionFlag) TODO: enable for compression { FileSystem::loadFileToMemory(_message, fileName); } else { _writeTextBody(_message, fileName); } } PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL4,"WebServer::handleWebRequest" "(WebRequest* webRequest) - response:\n%s\n", Tracer::getHTTPRequestMessage(_message).get())); // create response message HTTPMessage* response = new HTTPMessage(_message, queueId); // set close connection flag response->setCloseConnect(true); // request-object is not needed any longer, free memory delete request; /* * send response */ _sendRepsonse(response); PEG_METHOD_EXIT(); } void WebProcessor::_sendError( int statusCode, Uint32& queueId, String debugMsg, String additionalHeaderFields) { Uint32 statusC = Uint32(statusCode); PEG_METHOD_ENTER(TRC_WEBSERVER, "WebProcessor::_sendError(Uint32 statusCode," " Uint32 queueId, String debugMsg)"); PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL1,"WebServer::_sendError" "(Uint32 statusCode, Uint32 queueId, String debugMsg) - " "statusCode: %d, QueueId: %d, debugMsg: %s", statusCode, queueId, (const char*)debugMsg.getCString())); // initialize response-buffer Buffer tmp = Buffer(DEFAULT_RESPONSE_BUFFER_SIZE); Buffer& _message = tmp; /* * create response */ _message << "HTTP/" << HTTP_VERSION << " " << statusC << " " << _getStatusMessage(statusC) << "\r\n"; // response Date _message << "Date: " << _getCurrentDate(); /* * additional header-fields required for certain errors */ if (additionalHeaderFields.size() > Uint32(0)) { _message << additionalHeaderFields << "\r\n"; } /* * handleError */ _message << "Content-Type: text/html;charset=UTF-8\r\n";//UTF-8 response _message << "\r\n\r\n"; // end of headers _message << getErrorPage(statusC, debugMsg); PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL1,"WebServer::_sendError" "(Uint32 statusCode, Uint32 queueId, String debugMsg) - " "response:\n%s\n", Tracer::getHTTPRequestMessage(_message).get())); // create response message HTTPMessage* response = new HTTPMessage(_message, queueId); // set close connection flag response->setCloseConnect(true); _sendRepsonse(response); PEG_METHOD_EXIT(); } void WebProcessor::_sendRepsonse(HTTPMessage* response) { PEG_METHOD_ENTER(TRC_WEBSERVER, "WebProcessor::_sendRepsonse(HTTPMessage* response)"); _webServer->handleResponse(response); PEG_METHOD_EXIT(); } String WebProcessor::getErrorPage(Uint32& statusCode, String& debugMsg) { String page = String(""); page.reserveCapacity(debugMsg.size() + 512);// 512 for the html-strings page.append("Error"); page.append("

"); page.append(_getStatusMessage(statusCode)); page.append("

"); #ifdef PEGASUS_DEBUG page.append(debugMsg); #endif /* PEGASUS_DEBUG */ page.append("\r\n"); return page; } Uint32 WebProcessor::_getFileNameForURI(String& requestURI, String& absPath) { PEG_METHOD_ENTER(TRC_WEBSERVER, "WebProcessor::_getFileNameForURI(String& requestURI, " "String& absPath)"); if (requestURI.size() == Uint32(0)) { // bad request, no uri supplied PEG_METHOD_EXIT(); return HTTP_STATUSCODE_BADREQUEST; } else if (requestURI.size() > MAX_URI_LENGTH) { // request uri too long PEG_METHOD_EXIT(); return HTTP_STATUSCODE_REQUESTURITOOLONG; } String fileName; Uint32 index = requestURI.find("?"); if (index != PEG_NOT_FOUND) {// cut parameters from URL ex: '?id=...&pb=..' fileName = requestURI.subString(0, index); } else { fileName = requestURI; } if (fileName == "/") {// map requests pointing to web-server's document-root to the index file fileName.append(_webConfig.getIndexFile()); } /* * construct fileName and validate */ String webRoot = _webConfig.getWebRoot(); String fullPath = (webRoot + fileName); String resolvedPath = FileSystem::getAbsoluteFileName(fullPath); if (FileSystem::isDirectory((const String&)resolvedPath)) {// it is not allowed to access directories, error 403 PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL1, "Directory access to %s is not allowed!", (const char*)resolvedPath.getCString())); PEG_METHOD_EXIT(); return HTTP_STATUSCODE_FORBIDDEN; } if (resolvedPath == "") { PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL1, "Cannot resolve file path %s", (const char*)fullPath.getCString())); PEG_METHOD_EXIT(); return HTTP_STATUSCODE_NOTFOUND; } /* * further checks can be placed here, for example to * prevent delivery of any config-files in case they are placed * in the webRoot. */ // clear first before appending absPath.clear(); /* * Ensure that the realpath starts with the web-root */ if (!resolvedPath.equalNoCase( (resolvedPath.subString(0, webRoot.size())), webRoot)) {// directory traversal attack PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL1, "Mismatch in resolved path: %s resolves to %s", (const char*)fullPath.getCString(), (const char*)resolvedPath.getCString())); PEG_METHOD_EXIT(); return HTTP_STATUSCODE_FORBIDDEN; } // append result absPath.append(resolvedPath); PEG_METHOD_EXIT(); return HTTP_STATUSCODE_OK; } void WebProcessor::_writeTextBody(Buffer& _message, String& fileName) { String line; ifstream infile(fileName.getCString()); if (!infile) { return; } while (GetLine(infile, line)) { // write UTF-8 _message << (const char*)line.getCString() << "\r\n"; } infile.close(); } Boolean WebProcessor::_getContentType(String& fileName, String& contentType) { PEG_METHOD_ENTER(TRC_WEBSERVER, "WebProcessor::_getContentType(String& fileName, " "String& contentType)"); //find last dot in filename int found = fileName.reverseFind('.'); if (found <= 0) {// security //error-case 'hidden file or file with no extension' return false; } String fileExtension = String(fileName.subString(found+1)); contentType.clear(); Boolean result = _webConfig.getMimeTypes().lookup(fileExtension, contentType); PEG_TRACE((TRC_WEBSERVER,Tracer::LEVEL4, "File extension is %s, contentType is %s", (const char*)fileExtension.getCString(), (const char*)contentType.getCString())); PEG_METHOD_EXIT(); return result; } char* WebProcessor::_getCurrentDate() { // time_t t; // time(&t); // return asctime(gmtime(&t)); time_t currentTime; struct tm* gmtTime; char* timeValue = (char*) malloc(30); time(¤tTime); gmtTime = gmtime(¤tTime); strftime(timeValue,30,"%a, %d %b %Y %H:%M:%S GMT",gmtTime); return timeValue; } char* WebProcessor::_getDateOfLastMod(String& fileName) { // last modified struct tm* modTime; // create a time structure struct stat attrib; // create a file attribute structure stat(fileName.getCString(), &attrib); // get the attributes modTime = gmtime(&(attrib.st_mtime)); // get the last modified time char* timeValue = (char*) malloc(30); strftime(timeValue,30,"%a, %d %b %Y %H:%M:%S GMT",modTime); return timeValue;//asctime(modTime); } String WebProcessor::_getStatusMessage(Uint32& statusCode) { String statusMsg; switch(statusCode) { /* * 2xx */ case HTTP_STATUSCODE_OK: statusMsg = HTTP_REASONPHRASE_OK; break; /* * 4xx */ case HTTP_STATUSCODE_BADREQUEST: statusMsg = HTTP_REASONPHRASE_BADREQUEST; break; case HTTP_STATUSCODE_FORBIDDEN: statusMsg = HTTP_REASONPHRASE_FORBIDDEN; break; case HTTP_STATUSCODE_NOTFOUND: statusMsg = HTTP_REASONPHRASE_NOTFOUND; break; case HTTP_STATUSCODE_METHODNOTALLOWED: statusMsg = HTTP_REASONPHRASE_METHODNOTALLOWED; break; case HTTP_STATUSCODE_NOTACCEPTABLE: statusMsg = HTTP_REASONPHRASE_NOTACCEPTABLE; break; case HTTP_STATUSCODE_REQUESTURITOOLONG: statusMsg = HTTP_REASONPHRASE_REQUESTURITOOLONG; break; /* * 5xx */ case HTTP_STATUSCODE_VERSIONNOTSUPPORTED: statusMsg = HTTP_REASONPHRASE_VERSIONNOTSUPPORTED; break; } return statusMsg; } Uint32 WebProcessor::_getRequestHeaderValue( String& headerLine, String& prefValue, String& resValue) { resValue.clear(); // default resValue.append(prefValue); if (headerLine.size() > Uint32(0)) { Array values = _split(headerLine, ","); Uint32 any = PEG_NOT_FOUND; // header line available ? if (values.size() == Uint32(0)) {// no, free choice any = 0; } else { // check requested mime types for (int i = 0; Uint32(i) < values.size(); i++) { Array valueDef = _split(values[i], ";"); // quality value present ? if (valueDef.size() > Uint32(1)) {// yes, get it String qFactorStr = valueDef[1]; // does the length make any sense ? (ex. q=0 & q=0.4) if (qFactorStr.size() > Uint32(2) && qFactorStr.size() < Uint32(5)) {// cast the value double qFactor; try { qFactor = atof(// remove 'q=' qFactorStr.subString( 2,qFactorStr.size()-1).getCString()); } catch(...) { return HTTP_STATUSCODE_BADREQUEST; } if (String::equal(valueDef[0], prefValue) && qFactor == 0) {// preferred value is explicitly excluded /* * TODO figure out if there's one of the other * request values acceptable for processing */ return HTTP_STATUSCODE_NOTACCEPTABLE; } } else {// invalid length of quality value return HTTP_STATUSCODE_BADREQUEST; } } else {// no quality value present means it is '1' if (String::equal(prefValue, valueDef[0])) {// the server-side preferred value is acceptable return HTTP_STATUSCODE_OK; } } } } } return HTTP_STATUSCODE_OK; } Array WebProcessor::_split(String& s, const char* delimiter) { Array result; Uint32 delimLength = strlen(delimiter); Boolean run = true; while(run) { Uint32 pos = s.find(delimiter); if (pos == PEG_NOT_FOUND) { pos = s.size(); run = false; } String res = s.subString(0, pos); result.append(res); s = s.subString(pos + delimLength); // remove leading whitespace while (s.size() > Uint32(0) && s[0] == " ") { s = s.subString(1); } } return result; } /* END */ PEGASUS_NAMESPACE_END