//%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 "CIMExportRequestDecoder.h" #include #include #include PEGASUS_USING_STD; PEGASUS_NAMESPACE_BEGIN CIMExportRequestDecoder::CIMExportRequestDecoder( MessageQueueService* outputQueue, Uint32 returnQueueId) : Base(PEGASUS_QUEUENAME_EXPORTREQDECODER), _outputQueue(outputQueue), _returnQueueId(returnQueueId), _serverTerminating(false) { } CIMExportRequestDecoder::~CIMExportRequestDecoder() { } void CIMExportRequestDecoder::sendResponse( Uint32 queueId, Buffer& message, Boolean closeConnect) { MessageQueue* queue = MessageQueue::lookup(queueId); if (queue) { HTTPMessage* httpMessage = new HTTPMessage(message); httpMessage->setCloseConnect(closeConnect); queue->enqueue(httpMessage); } } void CIMExportRequestDecoder::sendEMethodError( Uint32 queueId, HttpMethod httpMethod, const String& messageId, const String& eMethodName, const CIMException& cimException, Boolean closeConnect) { Buffer message; message = XmlWriter::formatSimpleEMethodErrorRspMessage( eMethodName, messageId, httpMethod, cimException); sendResponse(queueId, message,closeConnect); } void CIMExportRequestDecoder::sendHttpError( Uint32 queueId, const String& status, const String& cimError, const String& messageBody, Boolean closeConnect) { Buffer message; message = XmlWriter::formatHttpErrorRspMessage( status, cimError, messageBody); sendResponse(queueId, message,closeConnect); } void CIMExportRequestDecoder::handleEnqueue(Message* message) { PEGASUS_ASSERT(message != 0); switch (message->getType()) { case HTTP_MESSAGE: handleHTTPMessage((HTTPMessage*)message); break; default: PEGASUS_UNREACHABLE(PEGASUS_ASSERT(0);) break; } delete message; } void CIMExportRequestDecoder::handleEnqueue() { Message* message = dequeue(); if (message) handleEnqueue(message); } //------------------------------------------------------------------------------ // // From the HTTP/1.1 Specification (RFC 2626): // // Both types of message consist of a start-line, zero or more header fields // (also known as "headers"), an empty line (i.e., a line with nothing // preceding the CRLF) indicating the end of the header fields, and possibly // a message-body. // // Example CIM request: // // M-POST /cimom HTTP/1.1 // HOST: www.erewhon.com // Content-Type: application/xml; charset=utf-8 // Content-Length: xxxx // Man: http://www.dmtf.org/cim/operation ; ns=73 // 73-CIMExport: MethodRequest // 73-CIMExportMethod: ExportIndication // 73-CIMObject: root/cimv2 // //------------------------------------------------------------------------------ void CIMExportRequestDecoder::handleHTTPMessage(HTTPMessage* httpMessage) { PEGASUS_ASSERT(httpMessage->message.size() != 0); // Save queueId and userName Uint32 queueId = httpMessage->queueId; String userName = httpMessage->authInfo->getAuthenticatedUser(); Boolean closeConnect = httpMessage->getCloseConnect(); PEG_TRACE(( TRC_HTTP, Tracer::LEVEL4, "CIMOperationRequestDecoder::handleHTTPMessage() -" " httpMessage->getCloseConnect() returned %d", httpMessage->getCloseConnect())); // Parse the HTTP message: String startLine; Array headers; char* content; Uint32 contentLength; httpMessage->parse(startLine, headers, contentLength); // Parse the request line: String methodName; String requestUri; String httpVersion; HttpMethod httpMethod = HTTP_METHOD__POST; // ATTN-RK-P3-20020404: The requestUri may need to be pruned of the host // name. All we care about at this point is the path. HTTPMessage::parseRequestLine( startLine, methodName, requestUri, httpVersion); // // Set HTTP method for the request // if (methodName == "M-POST") { httpMethod = HTTP_METHOD_M_POST; } // Unsupported methods are caught in the HTTPAuthenticatorDelegator // //PEGASUS_ASSERT(methodName == "M-POST" || methodName == "POST"); if (methodName != "M-POST" && methodName != "POST") { sendHttpError( queueId, HTTP_STATUS_NOTIMPLEMENTED, "Only POST and M-POST are implemented", String::EMPTY, closeConnect); return; } // // // Not true: "Mismatch of method and version is caught in // HTTPAuthenticatorDelegator", bug #351 fixes this: // //PEGASUS_ASSERT (!((httpMethod == HTTP_METHOD_M_POST) && // (httpVersion == "HTTP/1.0"))); if ((httpMethod == HTTP_METHOD_M_POST) && (httpVersion == "HTTP/1.0")) { sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "M-POST method is not valid with version 1.0", String::EMPTY, closeConnect); return; } // // Process M-POST and POST messages: if (httpVersion == "HTTP/1.1") { // Validate the presence of a "Host" header. The HTTP/1.1 specification // says this in section 14.23 regarding the Host header field: // // All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad // Request) status code to any HTTP/1.1 request message which lacks // a Host header field. // // Note: The Host header value is not validated. const char* hostHeader; Boolean hostHeaderFound = HTTPMessage::lookupHeader( headers, "Host", hostHeader, false); if (!hostHeaderFound) { MessageLoaderParms parms( "ExportServer.CIMExportRequestDecoder.MISSING_HOST_HEADER", "HTTP request message lacks a Host header field."); String msg(MessageLoader::getMessage(parms)); sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "", msg, closeConnect); return; } } // Validate the "CIMExport" header: const char* cimExport; Boolean exportHeaderFound = HTTPMessage::lookupHeader( headers, "CIMExport", cimExport, true); // If the CIMExport header was missing, the HTTPAuthenticatorDelegator // would not have passed the message to us. // // PEGASUS_ASSERT(exportHeaderFound); if (!exportHeaderFound) { sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "Export header not found", String::EMPTY, closeConnect); return; } // if (System::strcasecmp(cimExport, "MethodRequest") != 0) { // The Specification for CIM Operations over HTTP reads: // 3.3.5. CIMExport // If a CIM Listener receives CIM Export request with this // header, but with a missing value or a value that is not // "MethodRequest", then it MUST fail the request with // status "400 Bad Request". The CIM Server MUST include a // CIMError header in the response with a value of // unsupported-operation. sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "unsupported-operation", String::EMPTY, closeConnect); return; } // Validate the "CIMExportBatch" header: const char* cimExportBatch; if (HTTPMessage::lookupHeader( headers, "CIMExportBatch", cimExportBatch, true)) { // The Specification for CIM Operations over HTTP reads: // 3.3.10. CIMExportBatch // If a CIM Listener receives CIM Export Request for which the // CIMExportBatch header is present, but the Listener does not // support Multiple Exports, then it MUST fail the request and // return a status of "501 Not Implemented". sendHttpError( queueId, HTTP_STATUS_NOTIMPLEMENTED, "multiple-requests-unsupported", String::EMPTY, closeConnect); return; } // Save these headers for later checking const char* cimProtocolVersion; if (!HTTPMessage::lookupHeader( headers, "CIMProtocolVersion", cimProtocolVersion, true)) { // Mandated by the Specification for CIM Operations over HTTP cimProtocolVersion = "1.0"; } const char* cimExportMethod; if (!HTTPMessage::lookupHeader( headers, "CIMExportMethod", cimExportMethod, true)) { // The CIMExportMethod header is not present and we already know the // Export Request Message is a Simple Export Request. sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "header-mismatch", String::EMPTY, closeConnect); return; } AcceptLanguageList acceptLanguages; ContentLanguageList contentLanguages; try { if (httpMessage->acceptLanguagesDecoded) { acceptLanguages = httpMessage->acceptLanguages; } else { // Get and validate the Accept-Language header, if set String acceptLanguageHeader; if (HTTPMessage::lookupHeader( headers, "Accept-Language", acceptLanguageHeader, false)) { acceptLanguages = LanguageParser::parseAcceptLanguageHeader( acceptLanguageHeader); } } if (httpMessage->contentLanguagesDecoded) { contentLanguages = httpMessage->contentLanguages; } else { // Get and validate the Content-Language header, if set String contentLanguageHeader; if (HTTPMessage::lookupHeader( headers, "Content-Language", contentLanguageHeader, false)) { contentLanguages = LanguageParser::parseContentLanguageHeader( contentLanguageHeader); } } } catch (Exception& e) { Thread::clearLanguages(); MessageLoaderParms msgParms( "ExportServer.CIMExportRequestDecoder.REQUEST_NOT_VALID", "request-not-valid"); String msg(MessageLoader::getMessage(msgParms)); sendHttpError( queueId, HTTP_STATUS_BADREQUEST, msg, e.getMessage(), closeConnect); return; } // Calculate the beginning of the content from the message size and // the content length. content = (char *) httpMessage->message.getData() + httpMessage->message.size() - contentLength; // Validate the "Content-Type" header: const char* cimContentType; Boolean contentTypeHeaderFound = HTTPMessage::lookupHeader( headers, "Content-Type", cimContentType, true); String type; String charset; if (!contentTypeHeaderFound || !HTTPMessage::parseContentTypeHeader(cimContentType, type, charset) || (!String::equalNoCase(type, "application/xml") && !String::equalNoCase(type, "text/xml")) || !String::equalNoCase(charset, "utf-8")) { sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "", "HTTP Content-Type header error.", closeConnect); return; } else { // Validating content falls within UTF8 (required to be complaint // with section C12 of Unicode 4.0 spec, chapter 3.) Uint32 count = 0; while(countipAddress, acceptLanguages, contentLanguages, closeConnect); } void CIMExportRequestDecoder::handleMethodRequest( Uint32 queueId, HttpMethod httpMethod, char* content, const String& requestUri, const char* cimProtocolVersionInHeader, const char* cimExportMethodInHeader, const String& userName, const String& ipAddress, const AcceptLanguageList& httpAcceptLanguages, const ContentLanguageList& httpContentLanguages, Boolean closeConnect) { // Set the Accept-Language into the thread for this service. // This will allow all code in this thread to get // the languages for the messages returned to the client. Thread::setLanguages(httpAcceptLanguages); // // If CIM Listener is shutting down, return error response // if (_serverTerminating) { sendHttpError( queueId, HTTP_STATUS_SERVICEUNAVAILABLE, String::EMPTY, "CIM Listener is shutting down.", closeConnect); return; } // Create a parser: XmlParser parser(content); XmlEntry entry; String messageId; const char* cimExportMethodName = ""; AutoPtr request; try { // // Process // // These values are currently unused const char* xmlVersion = 0; const char* xmlEncoding = 0; XmlReader::getXmlDeclaration(parser, xmlVersion, xmlEncoding); // Expect const char* cimVersion = 0; const char* dtdVersion = 0; XmlReader::getCimStartTag(parser, cimVersion, dtdVersion); if (!XmlReader::isSupportedCIMVersion(cimVersion)) { sendHttpError( queueId, HTTP_STATUS_NOTIMPLEMENTED, "unsupported-cim-version", String::EMPTY, closeConnect); return; } if (!XmlReader::isSupportedDTDVersion(dtdVersion)) { sendHttpError( queueId, HTTP_STATUS_NOTIMPLEMENTED, "unsupported-dtd-version", String::EMPTY, closeConnect); return; } // Expect String protocolVersion; if (!XmlReader::getMessageStartTag( parser, messageId, protocolVersion)) { MessageLoaderParms mlParms( "ExportServer.CIMExportRequestDecoder.EXPECTED_MESSAGE_ELEMENT", "expected MESSAGE element"); throw XmlValidationError(parser.getLine(), mlParms); } // Validate that the protocol version in the header matches the XML if (!String::equalNoCase(protocolVersion, cimProtocolVersionInHeader)) { sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "header-mismatch", String::EMPTY, closeConnect); return; } if (!XmlReader::isSupportedProtocolVersion(protocolVersion)) { // See Specification for CIM Operations over HTTP section 4.3 sendHttpError( queueId, HTTP_STATUS_NOTIMPLEMENTED, "unsupported-protocol-version", String::EMPTY, closeConnect); return; } if (XmlReader::testStartTag(parser, entry, "MULTIEXPREQ")) { // We wouldn't have gotten here if CIMExportBatch header was // specified, so this must be indicative of a header mismatch sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "header-mismatch", String::EMPTY, closeConnect); return; // Future: When MULTIEXPREQ is supported, must ensure // CIMExportMethod header is absent, and CIMExportBatch header // is present. } // Expect XmlReader::expectStartTag(parser, entry, "SIMPLEEXPREQ"); // Expect if (!XmlReader::getEMethodCallStartTag(parser, cimExportMethodName)) { MessageLoaderParms mlParms( "ExportServer.CIMExportRequestDecoder." "EXPECTED_EXPMETHODCALL_ELEMENT", "expected EXPMETHODCALL element"); throw XmlValidationError(parser.getLine(), mlParms); } // The Specification for CIM Operations over HTTP reads: // 3.3.9. CIMExportMethod // // This header MUST be present in any CIM Export Request // message that contains a Simple Export Request. // // It MUST NOT be present in any CIM Export Response message, // nor in any CIM Export Request message that is not a // Simple Export Request. It MUST NOT be present in any CIM // Operation Request or Response message. // // The name of the CIM export method within a Simple Export // Request is defined to be the value of the NAME attribute // of the element. // // If a CIM Listener receives a CIM Export Request for which // either: // // - The CIMExportMethod header is present but has an invalid // value, or; // - The CIMExportMethod header is not present but the Export // Request Message is a Simple Export Request, or; // - The CIMExportMethod header is present but the Export // Request Message is not a Simple Export Request, or; // - The CIMExportMethod header is present, the Export Request // Message is a Simple Export Request, but the CIMIdentifier // value (when unencoded) does not match the unique method // name within the Simple Export Request, // // then it MUST fail the request and return a status of // "400 Bad Request" (and MUST include a CIMError header in the // response with a value of header-mismatch), subject to the // considerations specified in Errors. if (System::strcasecmp( cimExportMethodName, cimExportMethodInHeader) != 0) { // ATTN-RK-P3-20020404: How to decode cimExportMethodInHeader? sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "header-mismatch", String::EMPTY, closeConnect); return; } // This try block only catches CIMExceptions, because they must be // responded to with a proper EMETHODRESPONSE. Other exceptions are // caught in the outer try block. try { // Delegate to appropriate method to handle: if (System::strcasecmp( cimExportMethodName, "ExportIndication") == 0) { request.reset(decodeExportIndicationRequest( queueId, parser, messageId, requestUri)); } else { throw PEGASUS_CIM_EXCEPTION_L(CIM_ERR_NOT_SUPPORTED, MessageLoaderParms( "ExportServer.CIMExportRequestDecoder." "UNRECOGNIZED_EXPORT_METHOD", "Unrecognized export method: $0", cimExportMethodName)); } } catch (CIMException& e) { sendEMethodError( queueId, httpMethod, messageId, cimExportMethodName, e, closeConnect); return; } // Expect XmlReader::expectEndTag(parser, "EXPMETHODCALL"); // Expect XmlReader::expectEndTag(parser, "SIMPLEEXPREQ"); // Expect XmlReader::expectEndTag(parser, "MESSAGE"); // Expect XmlReader::expectEndTag(parser, "CIM"); } catch (XmlValidationError& e) { PEG_TRACE((TRC_XML,Tracer::LEVEL1, "CIMExportRequestDecoder::handleMethodRequest - " "XmlValidationError exception has occurred. Message: %s", (const char*) e.getMessage().getCString())); sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "request-not-valid", e.getMessage(), closeConnect); return; } catch (XmlSemanticError& e) { PEG_TRACE((TRC_XML,Tracer::LEVEL1, "CIMExportRequestDecoder::handleMethodRequest - " "XmlSemanticError exception has occurred. Message: %s", (const char*) e.getMessage().getCString())); // ATTN-RK-P2-20020404: Is this the correct response for these errors? sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "request-not-valid", e.getMessage(), closeConnect); return; } catch (XmlException& e) { PEG_TRACE((TRC_XML,Tracer::LEVEL1, "CIMExportRequestDecoder::handleMethodRequest - " "XmlException has occurred. Message: %s", (const char*) e.getMessage().getCString())); sendHttpError( queueId, HTTP_STATUS_BADREQUEST, "request-not-well-formed", e.getMessage(), closeConnect); return; } catch (Exception& e) { // Don't know why I got this exception. Seems like a bad thing. // Any exceptions we're expecting should be caught separately and // dealt with appropriately. This is a last resort. sendHttpError( queueId, HTTP_STATUS_INTERNALSERVERERROR, String::EMPTY, e.getMessage(), closeConnect); return; } catch (...) { // Don't know why I got whatever this is. Seems like a bad thing. // Any exceptions we're expecting should be caught separately and // dealt with appropriately. This is a last resort. sendHttpError( queueId, HTTP_STATUS_INTERNALSERVERERROR, String::EMPTY, String::EMPTY, closeConnect); return; } // l10n TODO - might want to move A-L and C-L to Message // to make this more maintainable // Add the language headers to the request. // Note: Since the text of an export error response will be ignored // by the export client, ignore Accept-Language in the export request. // This will cause any export error response message to be sent in the // default language. request->operationContext.insert(IdentityContainer(userName)); request->operationContext.set( ContentLanguageListContainer(httpContentLanguages)); request->operationContext.set( AcceptLanguageListContainer(AcceptLanguageList())); request->ipAddress = ipAddress; request->setCloseConnect(closeConnect); _outputQueue->enqueue(request.release()); } CIMExportIndicationRequestMessage* CIMExportRequestDecoder::decodeExportIndicationRequest( Uint32 queueId, XmlParser& parser, const String& messageId, const String& requestUri) { CIMInstance instanceName; String destStr = requestUri.subString( requestUri.find("/CIMListener") + 12, PEG_NOT_FOUND); for (const char* name; XmlReader::getEParamValueTag(parser, name);) { if (System::strcasecmp(name, "NewIndication") == 0) { XmlReader::getInstanceElement(parser, instanceName); } else { MessageLoaderParms mlParms( "ExportServer.CIMExportRequestDecoder." "UNRECOGNIZED_EXPPARAMVALUE_NAME", "Unrecognized EXPPARAMVALUE Name $0", name); throw PEGASUS_CIM_EXCEPTION(CIM_ERR_NOT_SUPPORTED, mlParms); } XmlReader::expectEndTag(parser, "EXPPARAMVALUE"); } CIMExportIndicationRequestMessage* request = new CIMExportIndicationRequestMessage( messageId, destStr, instanceName, QueueIdStack(queueId, _returnQueueId)); return request; } void CIMExportRequestDecoder::setServerTerminating(Boolean flag) { _serverTerminating = flag; } PEGASUS_NAMESPACE_END