virtual std::string retrieveResponse(const IHttpResponse& response)
 {
     if (response.getStatusCode() != HttpStatusCode::OK) {
         throw HttpTransportException(response.getStatusCode(), (boost::format("Invalid response code %1%") % response.getStatusCode()).str());
     }
     return getHttpDataProcessor()->retrieveOperationResponse(response);
 }
Errors::Code DefaultClientAuthorizationFacade::processAuthorizationRequest(const IHttpRequest& request, IHttpResponse &response) const
{
    if (!request.isParamExist(_acceptedFieldName))
    {
        make_error_response(Errors::Code::access_denied, "user denided access to client", request, response);
        return Errors::Code::access_denied;
    }

    if (!request.isParamExist(_userIdFieldName) || !request.isParamExist(Params::client_id) || !request.isParamExist(Params::scope))
    {
        make_error_response(Errors::Code::invalid_request, "no one or more required parameters user_id, client_id, scope", request, response);
        return Errors::Code::access_denied;
    }

    Grant grant(request.getParam(_userIdFieldName), request.getParam(Params::client_id), request.getParam(Params::scope));

    ServiceLocator::instance()->Storage->saveGrant(grant);

    //HACK: should use POST UserAuthenticationFacadeMock::_originalRequestFieldName parameter
    response.addHeader("Location", request.getHeader("Referer"));

    response.setStatus(302);

    return Errors::ok;
};
Ejemplo n.º 3
0
REQUEST_NOTIFICATION_STATUS CNodeHttpModule::OnExecuteRequestHandler(
    IN IHttpContext* pHttpContext, 
    IN IHttpEventProvider* pProvider)
{
        UNREFERENCED_PARAMETER( pProvider );

        // Create an HRESULT to receive return values from methods.
        HRESULT hr;

        // Retrieve a pointer to the response.
        IHttpResponse * pHttpResponse = pHttpContext->GetResponse();

        // Test for an error.
        if (pHttpResponse != NULL)
        {
            // Clear the existing response.
            pHttpResponse->Clear();
            // Set the MIME type to plain text.
            pHttpResponse->SetHeader(
                HttpHeaderContentType,"text/plain",
                (USHORT)strlen("text/plain"),TRUE);

            // Create a string with the response.
            PCSTR pszBuffer = "Hello World!";
            // Create a data chunk.
            HTTP_DATA_CHUNK dataChunk;
            // Set the chunk to a chunk in memory.
            dataChunk.DataChunkType = HttpDataChunkFromMemory;
            // Buffer for bytes written of data chunk.
            DWORD cbSent;

            // Set the chunk to the buffer.
            dataChunk.FromMemory.pBuffer =
                (PVOID) pszBuffer;
            // Set the chunk size to the buffer size.
            dataChunk.FromMemory.BufferLength =
                (USHORT) strlen(pszBuffer);
            // Insert the data chunk into the response.
            hr = pHttpResponse->WriteEntityChunks(
                &dataChunk,1,FALSE,TRUE,&cbSent);

            // Test for an error.
            if (FAILED(hr))
            {
                // Set the HTTP status.
                pHttpResponse->SetStatus(500,"Server Error",0,hr);
            }

            // End additional processing.
            return RQ_NOTIFICATION_FINISH_REQUEST;
        }

        // Return processing to the pipeline.
        return RQ_NOTIFICATION_CONTINUE;
}
Ejemplo n.º 4
0
apr_status_t WriteResponseCallback(request_rec *r, char *buf, unsigned int length)
{
	REQUEST_STORED_CONTEXT *rsc = RetrieveIISContext(r);

	if(rsc == NULL || rsc->m_pRequestRec == NULL || rsc->m_pResponseBuffer == NULL)
		return APR_SUCCESS;

	IHttpContext *pHttpContext = rsc->m_pHttpContext;
	IHttpResponse *pHttpResponse = pHttpContext->GetResponse();
	HTTP_RESPONSE *pRawHttpResponse = pHttpResponse->GetRawHttpResponse();
	HTTP_DATA_CHUNK *pDataChunk = (HTTP_DATA_CHUNK *)apr_palloc(rsc->m_pRequestRec->pool, sizeof(HTTP_DATA_CHUNK));

	pRawHttpResponse->EntityChunkCount = 0;

	// since we clean the APR pool at the end of OnSendRequest, we must get IIS-managed memory chunk
	//
	void *reqbuf = pHttpContext->AllocateRequestMemory(length);

	memcpy(reqbuf, buf, length);

	pDataChunk->DataChunkType = HttpDataChunkFromMemory;
	pDataChunk->FromMemory.pBuffer = reqbuf;
	pDataChunk->FromMemory.BufferLength = length;

    CHAR szLength[21]; //Max length for a 64 bit int is 20

    ZeroMemory(szLength, sizeof(szLength));

    HRESULT hr = StringCchPrintfA(
            szLength, 
            sizeof(szLength) / sizeof(CHAR) - 1, "%d", 
            length);

    if(FAILED(hr))
    {
		// not possible
    }

    hr = pHttpResponse->SetHeader(
            HttpHeaderContentLength, 
            szLength, 
            (USHORT)strlen(szLength),
            TRUE);

    if(FAILED(hr))
    {
		// possible, but there's nothing we can do
    }

	pHttpResponse->WriteEntityChunkByReference(pDataChunk);

	return APR_SUCCESS;
}
Ejemplo n.º 5
0
void HttpDataProcessor::verifyResponse(const IHttpResponse& response)
{
    if (response.getStatusCode() != 200) {
        throw TransportException(boost::format("Invalid response code %1%") % response.getStatusCode());
    }
    SharedBody rawResponse = response.getBody();
    const std::string& signature = response.getHeaderField("X-SIGNATURE");
    boost::scoped_array<std::uint8_t> decodedSignature(new std::uint8_t[signature.length() / 3 * 4]);
    size_t sigLength = Botan::base64_decode(decodedSignature.get(), signature);
    if (!encDec_->verifySignature(rawResponse.first.get(), rawResponse.second, decodedSignature.get(), sigLength)) {
        throw TransportException("Failed to verify signature");
    }
}
Ejemplo n.º 6
0
void make_error_response(const Errors::Code error, const string &msg, const IHttpRequest &request, IHttpResponse &response)
{
    typedef std::pair<string, string> jsonpair_t;

    response.setStatus(400);
    response.addHeader("Content-type","application/json; charset=utf-8");
    
    std::map<string, string> map;
    map.insert(jsonpair_t(Params::error,Errors::getText(error)));
    map.insert(jsonpair_t(Params::error_description,msg));

    response.setBody(mapToJSON(map));
};
Ejemplo n.º 7
0
REQUEST_NOTIFICATION_STATUS CIISxpressHttpModule::OnBeginRequest(IN IHttpContext* pHttpContext, IN IHttpEventProvider* pProvider)
{
    UNREFERENCED_PARAMETER(pProvider);    		

	const TCHAR* const pszMethodName = __FUNCTIONT__;

	// startup the performance counters
	if (m_Config.GetPerfCountersEnabled() && m_pInstancePerfCounters.get() == NULL)
	{
		AutoMemberCriticalSection lock(&m_csPerfCounters);

		if (m_pInstancePerfCounters.get() == NULL)
		{
			CAtlString sAppName;
			GetAppPoolName(pHttpContext, sAppName);

			m_pInstancePerfCounters = auto_ptr<IISxpressNativePerf>(new IISxpressNativePerf(sAppName));			
		}

		// TODO: maybe one day get the global counters to work
		//if (m_pGlobalPerfCounters.get() == NULL)
		//{
		//	m_pGlobalPerfCounters = auto_ptr<IISxpressNativePerf>(new IISxpressNativePerf());
		//}
	}

	IHttpResponse* pHttpResponse = pHttpContext->GetResponse();
	if (pHttpResponse != NULL)
	{
		// Disable caching as HANDLER_HTTPSYS_UNFRIENDLY.
		// TODO: is this really a good idea?
		pHttpResponse->DisableKernelCache();
	}

	// get the registry settings (will only update every 5000ms as default)
	GetRegistrySettings(pHttpContext);

	// only proceed if filter is enabled
	if (m_Config.GetEnabled() == false)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("module is disabled\n"));

		PerfCountersAddRejectedResponse(IISxpressNativePerf::FilterDisabled);

		// don't tell us about the send
		pHttpContext->DisableNotifications(RQ_SEND_RESPONSE, 0);
	}

    // Return processing to the pipeline.
    return RQ_NOTIFICATION_CONTINUE;
}
Ejemplo n.º 8
0
HRESULT CHttpProtocol::ParseResponseHeaders(CNodeHttpStoredContext* context)
{
	HRESULT hr;
	char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
	DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
	DWORD offset = 0;	
	DWORD nameEndOffset, valueEndOffset;
	IHttpResponse* response = context->GetHttpContext()->GetResponse();

	while (offset < (dataSize - 1) && data[offset] != 0x0D)
	{
		// header name

		nameEndOffset = offset;
		while (nameEndOffset < dataSize && data[nameEndOffset] != ':')
		{
			nameEndOffset++;
		}
		ErrorIf(nameEndOffset == dataSize, ERROR_MORE_DATA);

		// header value

		valueEndOffset = nameEndOffset + 1;
		while (valueEndOffset < (dataSize - 1) && data[valueEndOffset] != 0x0D)
		{
			valueEndOffset++;
		}
		ErrorIf(valueEndOffset >= dataSize - 1, ERROR_MORE_DATA);
		ErrorIf(0x0A != data[valueEndOffset + 1], ERROR_BAD_FORMAT);

		// set header on response
		
		data[nameEndOffset] = 0; // zero-terminate name to reuse without copying
		data[valueEndOffset] = 0; // zero-terminate header value because this is what IHttpResponse::SetHeader expects
		CheckError(response->SetHeader(data + offset, data + nameEndOffset + 1, valueEndOffset - nameEndOffset - 1, TRUE));
		//CheckError(response->SetHeader(data + offset, data + nameEndOffset + 2, valueEndOffset - nameEndOffset - 2, TRUE));

		// adjust offsets
		
		context->SetParsingOffset(context->GetParsingOffset() + valueEndOffset - offset + 2);
		offset = valueEndOffset + 2;
	}
	ErrorIf(offset >= dataSize - 1, ERROR_MORE_DATA);
	ErrorIf(0x0A != data[offset + 1], ERROR_BAD_FORMAT);

	context->SetParsingOffset(context->GetParsingOffset() + 2);

	return S_OK;
Error:
	return hr;
}
void DefaultClientAuthorizationFacade::makeAuthorizationRequestPage(const Grant &grant, const IHttpRequest &request,IHttpResponse &response) const
{
    string msg = DefaultClientAuthorizationFacade::_authzPageBody;

    //HACK: <<CONST>> should be moved to static const; clientId, scope, userId should be moved to <<params>> instead of text
    std::ostringstream ostr;
    ostr << "Client '" << grant.clientId << "' requested access to '" << grant.scope.str() << "' for logged user " << grant.userId;

    msg = std::regex_replace(msg, std::regex("<<Text>>"), ostr.str());
    msg = std::regex_replace(msg, std::regex("<<Action>>"), request.getRequestTarget()); //HACK: We don't need parameters consider using getHost+getPath

    // copy all request parameters to hidden form fields
    ostr.str("");
    ostr.clear();
    map<string,string> params = request.getParams();

    for (map<string,string>::const_iterator it = params.begin(); it != params.end(); ++it)
        ostr << "<input type='hidden' name='" << it->first << "' value='" << it->second << "'>";

    ostr << "<input type='hidden' name='" << _userIdFieldName << "' value='" << grant.userId << "'>";
    ostr << "<input type='hidden' name='" << authorizationFormMarker << "'>";

    msg = std::regex_replace(msg, std::regex("<<HiddenFormValues>>"), ostr.str());
    msg = std::regex_replace(msg, std::regex("<<AcceptFieldName>>"), _acceptedFieldName);

    response.setBody(msg);
};
Ejemplo n.º 10
0
REQUEST_NOTIFICATION_STATUS CCacheHelperModule::OnSendResponse(
    IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProvider)
{
    UNREFERENCED_PARAMETER(pProvider);

    IHttpResponse* pHttpResponse = pHttpContext->GetResponse();
    IHttpRequest* pHttpRequest = pHttpContext->GetRequest();

    if ((pHttpResponse != NULL) && (pHttpRequest != NULL))
    {
        HTTP_COOKED_URL url = pHttpRequest->GetRawHttpRequest()->CookedUrl;

        if (!m_pConfig->UrlHasSitePrefix(url.pAbsPath))
            return RQ_NOTIFICATION_CONTINUE;

        CachingBehavior behavior = CachingBehaviorIgnore;

        USHORT cchXCache;
        PCSTR szXCache = pHttpResponse->GetHeader((PCSTR)"X-Cache", &cchXCache);
        if (cchXCache > 0)
        {
            behavior = (CachingBehavior)StrToIntA(szXCache);
            pHttpResponse->DeleteHeader((PCSTR)"X-Cache");
        }
        else
        {
            behavior = m_pConfig->GetCachePolicy(url.pAbsPath);
        }
        
        switch (behavior)
        {
        case CachingBehaviorPrivate:
            MakeUncacheable(pHttpResponse);
            break;
        case CachingBehaviorConditional:
            MakeConditionallyCacheable(pHttpResponse);
            break;
        case CachingBehaviorPublic:
            MakeCacheable(pHttpResponse);
            break;
        }
    }

    return RQ_NOTIFICATION_CONTINUE;
}
Ejemplo n.º 11
0
REQUEST_NOTIFICATION_STATUS CIISxpressHttpModule::OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProvider)
{
	const TCHAR* const pszMethodName = __FUNCTIONT__;	

	// only proceed if filter is enabled
	if (m_Config.GetEnabled() == false)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("module is disabled\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::FilterDisabled);
		return RQ_NOTIFICATION_CONTINUE;
	}

	IHttpRequest* pHttpRequest = pHttpContext->GetRequest();
	if (pHttpRequest == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetRequest() returned NULL\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}	

	IHttpResponse* pHttpResponse = pHttpContext->GetResponse();
	if (pHttpResponse == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetResponse() returned NULL\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}	

	const HTTP_REQUEST* pRawRequest = pHttpRequest->GetRawHttpRequest();
	if (pRawRequest == NULL)
	{		
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetRawHttpRequest() returned NULL\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}		

	const HTTP_RESPONSE* pRawResponse = pHttpResponse->GetRawHttpResponse();
	if (pRawResponse == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetRawHttpResponse() returned NULL\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}		

	// we only handle GET or POST (if POST handling is enabled)
	if (pRawRequest->Verb != HttpVerbGET && !(pRawRequest->Verb == HttpVerbPOST && m_Config.HandlePOSTResponses()))
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("request was not GET or POST\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::RequestMethod);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// only handle status code 200
	if (pRawResponse->StatusCode != 200)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("response code is not 200 (%u)\n"),  pRawResponse->StatusCode);
		PerfCountersAddRejectedResponse(IISxpressNativePerf::ResponseCode);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// determine if the request came from localhost
	if (m_Config.GetCompressLocalhost() == false && IsUserLocalhost(pRawRequest) == true)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext,_T("localhost is disabled\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::LocalhostDisabled);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// ***********************************************************************************************************************************

	const char* pszUserAgent = EnsureNotNull(pHttpRequest->GetHeader(HttpHeaderUserAgent));	

	bool excludedUserAgent = false;
	if (m_Config.GetUserAgentExclusionEnabled())
	{
		DWORD dwUserAgentCacheCookie = m_Config.GetLoadCookie();
		
		if (!m_UserAgentCache.GetUserAgentState(dwUserAgentCacheCookie, pszUserAgent, excludedUserAgent))
		{
			HttpUserAgent::UserAgentProducts<std::string> agent;
			if (agent.ParseUserAgentString(pszUserAgent) == S_OK)
			{
				const HttpUserAgent::RuleUserAgents<std::string>& ruleAgents = m_Config.GetExcludedUserAgents();
				if (ruleAgents.Compare(agent))
				{
					excludedUserAgent = true;
				}

				m_UserAgentCache.AddUserAgentState(dwUserAgentCacheCookie, pszUserAgent, excludedUserAgent);
			}
		}
	}

	if (excludedUserAgent)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("the user agent '%s' has been excluded\n"),  pszUserAgent);		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::NeverRuleMatch);
		return RQ_NOTIFICATION_CONTINUE;
	}	

	// ***********************************************************************************************************************************

	// we must have only one chunk and data from memory or file
	if (pRawResponse->EntityChunkCount == 0)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext,_T("can't convert multi-entity buffer, incoming buffer is zero length\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::InvalidContentLength);
		return RQ_NOTIFICATION_CONTINUE;
	}
	else if (pRawResponse->EntityChunkCount != 1)
	{		
		// turn the multi-chunk response into a single chunk - NB. the response
		// must all be in memory
		if (MakeResponseSingleEntityBlock(pHttpContext, pHttpResponse, pRawResponse) == false)
		{
			AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext,_T("can't convert multi-entity buffer\n"));
			PerfCountersAddRejectedResponse(IISxpressNativePerf::MemoryAllocationFailed);
			return RQ_NOTIFICATION_CONTINUE;
		}				
	}	
	else if (pRawResponse->pEntityChunks->DataChunkType != HttpDataChunkFromMemory &&
			pRawResponse->pEntityChunks->DataChunkType != HttpDataChunkFromFileHandle)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext,_T("response isn't HttpDataChunkFromMemory or HttpDataChunkFromFileHandle\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}

	PHTTP_DATA_CHUNK pEntityChunk = pRawResponse->pEntityChunks;

	if (pEntityChunk == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("the response does not contain any data\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;	
	}

	// TODO: review max size here
	// we don't handle non-zero starting offsets or very large files (>250MB)
	if (pEntityChunk->DataChunkType == HttpDataChunkFromFileHandle &&
		(pEntityChunk->FromFileHandle.ByteRange.StartingOffset.QuadPart > 0 ||
		pEntityChunk->FromFileHandle.ByteRange.Length.QuadPart > (250 * 1024 * 1024)))
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("response has offset > 0 or is too big\n"));
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}

	PCSTR pszContentEncoding = pHttpResponse->GetHeader(HttpHeaderContentEncoding);
	if (pszContentEncoding != NULL && pszContentEncoding[0] != '\0')
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("response already has content encoding\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::AlreadyEncoded);
		return RQ_NOTIFICATION_CONTINUE;
	}

	PCSTR pszTransferEncoding = pHttpResponse->GetHeader(HttpHeaderTransferEncoding);
	if (pszTransferEncoding != NULL && pszTransferEncoding[0] != '\0')
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("response already has transfer encoding\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::AlreadyEncoded);
		return RQ_NOTIFICATION_CONTINUE;
	}

	USHORT nContentTypeLength = 0;
	PCSTR pszContentType = pHttpResponse->GetHeader(HttpHeaderContentType, &nContentTypeLength);			
	if (pszContentType == NULL || nContentTypeLength == 0)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetHeader(HttpHeaderContentType) returned NULL\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::MissingContentType);
		return RQ_NOTIFICATION_CONTINUE;
	}

	USHORT nLastModifiedLength = 0;
	PCSTR pszLastModified = pHttpResponse->GetHeader(HttpHeaderLastModified, &nLastModifiedLength);			

	// ***********************************************************************
	
	USHORT nAcceptEncodingLength = 0;
	PCSTR pszAcceptEncoding = pHttpRequest->GetHeader(HttpHeaderAcceptEncoding, &nAcceptEncodingLength);	
	if (pszAcceptEncoding == NULL || nAcceptEncodingLength == 0 ||
		(strstr(pszAcceptEncoding, "deflate") == NULL && strstr(pszAcceptEncoding, "gzip") == NULL && strstr(pszAcceptEncoding, "bzip2") == NULL))
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("the client does not accept compressed responses\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::IncompatibleClient);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// ***********************************************************************

	const WCHAR* pszScriptTranslated = pHttpContext->GetScriptTranslated();
	if (pszScriptTranslated == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetScriptTranslated() returned NULL\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}
		
	// get the url
	CAtlStringA sScriptTranslated(pszScriptTranslated);

	// get the site id
	CAtlStringA sInstanceId;
	sInstanceId.Format("%u", pHttpRequest->GetSiteId());
	
	// get the server name
	PCSTR pszServerName = NULL;		
	DWORD dwServerNameLength = 0;
	pHttpContext->GetServerVariable("SERVER_NAME", &pszServerName, &dwServerNameLength);
	if (pszServerName == NULL || dwServerNameLength == 0)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetServerVariable(\"SERVER_NAME\") returned NULL\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// get the server port 		
	PCSTR pszServerPort = NULL;		
	DWORD dwServerPortLength = 0;
	pHttpContext->GetServerVariable("SERVER_PORT", &pszServerPort, &dwServerPortLength);
	if (pszServerPort == NULL || dwServerPortLength == 0)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetServerVariable(\"SERVER_PORT\") returned NULL\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}

	DWORD dwContentLength = 0;
	if (pEntityChunk->DataChunkType == HttpDataChunkFromMemory)
	{
		dwContentLength = pRawResponse->pEntityChunks->FromMemory.BufferLength;
	}
	else if (pEntityChunk->DataChunkType == HttpDataChunkFromFileHandle)
	{
		dwContentLength = pEntityChunk->FromFileHandle.ByteRange.Length.LowPart;
	}
	else
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("the DataChunkType (%u) is not supported\n"), pEntityChunk->DataChunkType);		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}
	
	IISInfo iisinfo;
	iisinfo.pszInstanceId = sInstanceId;		
	iisinfo.pszServerName = pszServerName;
	iisinfo.pszServerPort = pszServerPort;
	iisinfo.pszURLMapPath = sScriptTranslated;		

	if (m_Config.GetDebugEnabled() || m_Config.GetLoggingLevel() >= IISXPRESS_LOGGINGLEVEL_ENH)	
	{				
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("IIS: server name='%hs'\n"), pszServerName);		
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("IIS: server port='%hs'\n"), pszServerPort);
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("IIS: instance id='%hs'\n"), sInstanceId);
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("IIS: URL map path='%hs'\n"), sScriptTranslated);
	}			

	// ***********************************************************************

	const WCHAR* pszURI = pHttpContext->GetScriptName();
	if (pszURI == NULL)
	{
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("GetScriptName() returned NULL\n"));		
		PerfCountersAddRejectedResponse(IISxpressNativePerf::Internal);
		return RQ_NOTIFICATION_CONTINUE;
	}

	// get the uri
	CAtlStringA sURI(pszURI);

	// get the query string
	CAtlStringA sQueryString;
	if (pRawRequest->CookedUrl.pQueryString != NULL)
	{
		sQueryString = pRawRequest->CookedUrl.pQueryString + 1;
	}

	// get the remote address
	// TODO: consider using a cache lookup here 
	CAtlStringA sRemoteAddress;
	if (pRawRequest != NULL && pRawRequest->Address.pRemoteAddress != NULL)			
	{
		if (pRawRequest->Address.pRemoteAddress->sa_family == AF_INET)
		{
			struct sockaddr_in* paddrin = (struct sockaddr_in*) pRawRequest->Address.pRemoteAddress;

			// TODO: make this a bit more efficient
			sRemoteAddress.Format("%u.%u.%u.%u", 
				(unsigned) paddrin->sin_addr.S_un.S_un_b.s_b1,
				(unsigned) paddrin->sin_addr.S_un.S_un_b.s_b2,
				(unsigned) paddrin->sin_addr.S_un.S_un_b.s_b3,
				(unsigned) paddrin->sin_addr.S_un.S_un_b.s_b4);
		}
		else if (pRawRequest->Address.pRemoteAddress->sa_family == AF_INET6)
		{
			SOCKADDR_IN6* paddrin = (SOCKADDR_IN6*) pRawRequest->Address.pRemoteAddress;

			// TODO: make this a bit more efficient
			sRemoteAddress.Format("%0x:%0x:%0x:%0x:%0x:%0x:%0x:%0x",				
				::htons(paddrin->sin6_addr.u.Word[0]), ::htons(paddrin->sin6_addr.u.Word[1]),
				::htons(paddrin->sin6_addr.u.Word[2]), ::htons(paddrin->sin6_addr.u.Word[3]),
				::htons(paddrin->sin6_addr.u.Word[4]), ::htons(paddrin->sin6_addr.u.Word[5]),
				::htons(paddrin->sin6_addr.u.Word[6]), ::htons(paddrin->sin6_addr.u.Word[7]));
		}
	}	
		
	RequestInfo requestinfo;				
	requestinfo.pszAcceptEncoding = pszAcceptEncoding;
	requestinfo.pszHostname = EnsureNotNull(pHttpRequest->GetHeader(HttpHeaderHost));
	requestinfo.pszQueryString = sQueryString;
	requestinfo.pszRemoteAddress = sRemoteAddress;
	requestinfo.pszURI = sURI;
	requestinfo.pszUserAgent = pszUserAgent;

	if (m_Config.GetDebugEnabled() || m_Config.GetLoggingLevel() >= IISXPRESS_LOGGINGLEVEL_BASIC)	
	{				
		char* pszMethod = "Unknown";		
		switch (pRawRequest->Verb)
		{
		case HttpVerbGET: pszMethod = "GET"; break;
		case HttpVerbPOST: pszMethod = "POST"; break;
		}

		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: method='%hs'\n"), pszMethod);		
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: hostname='%hs'\n"), requestinfo.pszHostname);		
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: user agent='%hs'\n"), requestinfo.pszUserAgent);		
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: URI='%hs'\n"), requestinfo.pszURI);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: accept-encoding='%hs'\n"), requestinfo.pszAcceptEncoding);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: client address='%hs'\n"), requestinfo.pszRemoteAddress);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: query string='%hs'\n"), requestinfo.pszQueryString);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: content-type='%hs'\n"), pszContentType);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: content-length=%u\n"), dwContentLength);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: response code=%u\n"), pRawResponse->StatusCode);	
		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("REQUEST: last-modified='%hs'\n"), pszLastModified != NULL ? pszLastModified : "null");	
	}			

	// ***********************************************************************	
	
	ResponseInfo responseinfo;		
	responseinfo.dwContentLength = dwContentLength;
	responseinfo.dwResponseCode = pRawResponse->StatusCode;	
	responseinfo.pszContentType = pszContentType;
	responseinfo.pszLastModified = pszLastModified;

	// get a connection to the server from the GIT (if it is connected)
	CComPtr<IIISxpressHTTPRequest> pIISxpressHTTPRequest;
	if (m_pGlobalIISxpressHTTPRequest.GetCookie() != 0)	
	{
		m_pGlobalIISxpressHTTPRequest.CopyTo(&pIISxpressHTTPRequest);
	}

	if (pIISxpressHTTPRequest == NULL)
	{
		// revoke the pointer in the GIT
		m_pGlobalIISxpressHTTPRequest.Revoke();

		// try to connect to the server
		if (GetHTTPRequestObject(pHttpContext, &pIISxpressHTTPRequest) == false || pIISxpressHTTPRequest == NULL)	
		{
			AppendLogMessage(IISXPRESS_LOGGINGLEVEL_ENH, pszMethodName, pHttpContext, _T("unable to connect to server\n"));					
			PerfCountersAddRejectedResponse(IISxpressNativePerf::NoCompressionServer);
			return RQ_NOTIFICATION_CONTINUE;
		}		

		// store the resulting pointer in the GIT
		m_pGlobalIISxpressHTTPRequest = pIISxpressHTTPRequest;
	}			

	const DWORD dwTimerLoggingLevel = IISXPRESS_LOGGINGLEVEL_FULL;
	bool useTimer = (m_Config.GetDebugEnabled() || m_Config.GetLoggingLevel() >= dwTimerLoggingLevel);

	// start timer
	__int64 nStartTimer = useTimer ? g_Timer.GetMicroSecTimerCount() : 0;

	CAtlStringA sCacheKey;
	if (m_nCacheEnabled != 0)
	{	
		// update the cache perf counters based on the cache cookie
		PerfCountersUpdateCacheStatus(false);	

		if (pszLastModified != NULL && sQueryString.GetLength() == 0)
		{
			ATLASSERT(pszAcceptEncoding != NULL);

			// create the unique key for the response
			ResponseCache::CreateResponseCacheKey(
				sInstanceId, sInstanceId.GetLength(),
				sURI, sURI.GetLength(),
				responseinfo.pszContentType, nContentTypeLength,
				dwContentLength, 
				pszLastModified, nLastModifiedLength,
				pszAcceptEncoding, nAcceptEncodingLength,
				sCacheKey);

			// try to get a compressed response from the cache
			HCACHEITEM hCacheItem = NULL;
			HRESULT hr = m_ResponseCache.LookupEntry(sCacheKey, &hCacheItem);
			if (hr == S_OK)
			{
				// get the item from the cache
				ResponseCacheItem* pCacheItem;
				DWORD dwCacheItemSize = 0;
				hr = m_ResponseCache.GetData(hCacheItem, (void**) &pCacheItem, &dwCacheItemSize);	

				void* pBuffer = pHttpContext->AllocateRequestMemory(pCacheItem->dwContentLength);
				if (pBuffer != NULL)
				{
					pHttpResponse->SetHeader(HttpHeaderContentLength, pCacheItem->sContentLength, 
						(USHORT) pCacheItem->sContentLength.GetLength(), TRUE);

					pHttpResponse->SetHeader(HttpHeaderContentEncoding, pCacheItem->sContentEncoding, 
						(USHORT) pCacheItem->sContentEncoding.GetLength(), TRUE);

					pEntityChunk->DataChunkType = HttpDataChunkFromMemory;
					pEntityChunk->FromMemory.pBuffer = pBuffer;
					pEntityChunk->FromMemory.BufferLength = pCacheItem->dwContentLength;
					memcpy(pEntityChunk->FromMemory.pBuffer, pCacheItem->pbyData, pCacheItem->dwContentLength);

					hr = m_ResponseCache.ReleaseEntry(hCacheItem);

					pIISxpressHTTPRequest->NotifyCacheHit(&iisinfo, &requestinfo, &responseinfo, pCacheItem->dwContentLength);

					PerfCountersAddCachedResponse(dwContentLength, pCacheItem->dwContentLength);

					// calculate timer duration
					__int64 nEndTimer = useTimer ? g_Timer.GetMicroSecTimerCount() : 0;
					__int64 nInterval = nEndTimer - nStartTimer;

					AppendLogMessage(dwTimerLoggingLevel, pszMethodName, pHttpContext, _T("response resolved from cache, call took %I64d us\n"), nInterval);

					return RQ_NOTIFICATION_CONTINUE;
				}
				
				// we failed to handle the cached response, so just proceed as normal (we need to free the
				// cache entry tho)
				hr = m_ResponseCache.ReleaseEntry(hCacheItem);
			}		
		}
	}

	DWORD dwFilterContext = 0;
	HRESULT hr = pIISxpressHTTPRequest->OnSendResponse(&iisinfo, &requestinfo, &responseinfo, &dwFilterContext);

	// calculate timer duration
	__int64 nEndTimer = useTimer ? g_Timer.GetMicroSecTimerCount() : 0;
	__int64 nInterval = nEndTimer - nStartTimer;

	if (FAILED(hr) == TRUE)
	{
		// get the facility code of the failure
		DWORD dwFacility = HRESULT_FACILITY(hr);

		// if it isn't NULL or ITF then assume the server is now invalid
		if (dwFacility != FACILITY_NULL && dwFacility != FACILITY_ITF)
		{
			// the GIT is invalid, dump it
			m_pGlobalIISxpressHTTPRequest.Revoke();

			// the server connection is invalid, dump it
			pIISxpressHTTPRequest = NULL;		
		}		
	}

	AppendLogMessage(dwTimerLoggingLevel, pszMethodName, pHttpContext, _T("OnSendResponse() returns 0x%08x, call took %I64d us\n"), hr, nInterval);	

	if (FAILED(hr) == TRUE)
	{			
		// TODO: need to handle generic HRs
		PerfCountersAddRejectedResponse(hr);

		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("won't handle response (0x%08x)\n"), hr);					

		return RQ_NOTIFICATION_CONTINUE;
	}

	AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("will handle response (0x%08x)\n"), hr);		

	if (pEntityChunk->DataChunkType == HttpDataChunkFromFileHandle)
	{
		DWORD dwAllocatedBlockSize = 0;
		BYTE* pbyData = AllocateMemoryBlockForOverlappedIO(pHttpContext, dwContentLength, dwAllocatedBlockSize);
		if (pbyData != NULL)
		{		
			// we are about to perform overlapped IO, so setup the OVERLAPPED struct into a default state
			OVERLAPPED overlapped;
			memset(&overlapped, 0, sizeof(overlapped));

			HandleObject FileEvent;
			bool bHandleFromPool = m_hEventHandlePool.GetHandleObject(FileEvent);									

			// if we failed to get a handle from the pool then we need to make one
			if (bHandleFromPool == false)
			{
				HANDLE hReadOK = ::CreateEvent(NULL, TRUE, FALSE, NULL);
				FileEvent = HandleObject(hReadOK);
			}

			// use the event handle so we can track the IO completion
			overlapped.hEvent = FileEvent;

			// ask for a file read (asynchronous)		
			DWORD dwStatus = ::ReadFile(pEntityChunk->FromFileHandle.FileHandle, pbyData, dwAllocatedBlockSize, NULL, &overlapped);

			// wait for the IO to complete (NB. don't really use this since INFINITE can block forever)			
			::WaitForSingleObject(FileEvent, INFINITE);

			// we must return the handle (if it came from the pool)
			if (bHandleFromPool == true)
			{
				m_hEventHandlePool.ReturnHandleObject(FileEvent);
			}

			pEntityChunk->DataChunkType = HttpDataChunkFromMemory;
			pEntityChunk->FromMemory.BufferLength = dwContentLength;
			pEntityChunk->FromMemory.pBuffer = pbyData;
		}
		else
		{
			hr = E_OUTOFMEMORY;
		}
	}
	else if (pEntityChunk->DataChunkType == HttpDataChunkFromMemory)
	{
		// allocate the memory block
		BYTE* pbyData = (BYTE*) pHttpContext->AllocateRequestMemory(dwContentLength);			
		if (pbyData != NULL)
		{
			// copy the response data
			memcpy(pbyData, pEntityChunk->FromMemory.pBuffer, pEntityChunk->FromMemory.BufferLength);

			// change the buffer pointer
			pEntityChunk->FromMemory.pBuffer = pbyData;
		}
		else
		{
			hr = E_OUTOFMEMORY;
		}
	}		

	// allocate the response stream
	CComPtr<IStream> pStream;
	CComObject<CResponseStream>* pResponseStream = NULL;	
	if (hr == S_OK)
	{
		hr = CComObject<CResponseStream>::CreateInstance(&pResponseStream);
		if (hr == S_OK)
		{		
			pStream = pResponseStream;
		}
	}

	// catch any memory issues
	if (FAILED(hr) == TRUE)
	{			
		// abort the compression
		pIISxpressHTTPRequest->AbortRequest(dwFilterContext);

		// TODO: need to handle generic HRs
		PerfCountersAddRejectedResponse(hr);

		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("won't handle response (0x%08x)\n"), hr);					

		return RQ_NOTIFICATION_CONTINUE;
	}

	pResponseStream->AttachBuffer(pEntityChunk->FromMemory.pBuffer, dwContentLength, 0);

	char szContentEncoding[32] = "";
	hr = pIISxpressHTTPRequest->OnSendRawData(dwFilterContext, pStream, dwContentLength, FALSE, (signed char*) szContentEncoding, _countof(szContentEncoding));
	if (hr == S_OK)
	{		
		DWORD dwOriginalSize = dwContentLength;
		DWORD dwCompressedSize = pResponseStream->GetOffset();				

		// set the new content length into the header
		CAtlStringA sContentLength;
		sContentLength.Format("%u", dwCompressedSize);
		pHttpResponse->SetHeader(HttpHeaderContentLength, sContentLength, (USHORT) sContentLength.GetLength(), TRUE);

		// set the new size into the buffer
		pRawResponse->pEntityChunks->FromMemory.BufferLength = dwCompressedSize;
		
		pHttpResponse->SetHeader(HttpHeaderContentEncoding, szContentEncoding, (USHORT) strlen(szContentEncoding), TRUE);

		// add the item to the cache if we have a key (it means the data is cachable)
		if (dwCompressedSize < dwOriginalSize &&
			m_nCacheEnabled != 0 && sCacheKey.GetLength() > 0)
		{
			ResponseCacheItem* pCacheItem = new ResponseCacheItem();
			if (pCacheItem != NULL)
			{
				pCacheItem->sContentEncoding = szContentEncoding;
				pCacheItem->sContentLength = sContentLength;
				pCacheItem->dwContentLength = dwCompressedSize;
				pCacheItem->pbyData = new BYTE[dwCompressedSize];
				if (pCacheItem->pbyData != NULL)
				{
					// copy the compressed data and add it to the cache
					memcpy(pCacheItem->pbyData, pEntityChunk->FromMemory.pBuffer, dwCompressedSize);
					m_ResponseCache.Add(sCacheKey, pCacheItem, pCacheItem->dwContentLength, NULL, NULL, NULL, m_ResponseCacheItemDeallocator);

					PerfCountersUpdateCacheStatus(true);
				}
			}
		}

		PerfCountersAddCompressedResponse(dwContentLength, dwCompressedSize);

		// make sure the context is released
		hr = pIISxpressHTTPRequest->OnEndOfRequest(dwFilterContext, NULL, 0, FALSE, NULL, 0);		
	}
	else
	{
		// abort the compression
		pIISxpressHTTPRequest->AbortRequest(dwFilterContext);

		// TODO: need to handle generic HRs
		PerfCountersAddRejectedResponse(hr);

		AppendLogMessage(IISXPRESS_LOGGINGLEVEL_BASIC, pszMethodName, pHttpContext, _T("won't handle response (0x%08x)\n"), hr);							
	}

	return RQ_NOTIFICATION_CONTINUE;
}
Ejemplo n.º 12
0
std::string HttpDataProcessor::retrieveOperationResponse(const IHttpResponse& response)
{
    SharedBody rawResponse = response.getBody();
    return encDec_->decodeData(rawResponse.first.get(), rawResponse.second);
}
Ejemplo n.º 13
0
HRESULT CHttpProtocol::ParseResponseStatusLine(CNodeHttpStoredContext* context)
{
	HRESULT hr;

	char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
	DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
	DWORD offset = 0;	
	USHORT major, minor;
	DWORD count, newOffset;
	char tmp[256];
	USHORT statusCode, subStatusCode = 0;

	// HTTP-Version SP

	context->GetHttpContext()->GetRequest()->GetHttpVersion(&major, &minor);
	sprintf(tmp, "HTTP/%d.%d ", major, minor);
	count = strlen(tmp);
	ErrorIf(count >= dataSize, ERROR_MORE_DATA);
	ErrorIf(0 != memcmp(tmp, data, count), ERROR_BAD_FORMAT);
	offset += count;

	// Status-Code[.Sub-Status-Code] SP

	statusCode = 0;
	while (offset < dataSize && data[offset] >= '0' && data[offset] <= '9')
	{
		statusCode = statusCode * 10 + data[offset++] - '0';
	}
	ErrorIf(offset == dataSize, ERROR_MORE_DATA);

	if ('.' == data[offset])
	{
		// Sub-Status-Code

		offset++;		

		while (offset < dataSize && data[offset] >= '0' && data[offset] <= '9')
		{
			subStatusCode = subStatusCode * 10 + data[offset++] - '0';
		}
		ErrorIf(offset == dataSize, ERROR_MORE_DATA);
	}

	ErrorIf(' ' != data[offset], ERROR_BAD_FORMAT);
	offset++;

	// Reason-Phrase CRLF

	newOffset = offset;
	while (newOffset < (dataSize - 1) && data[newOffset] != 0x0D)
	{
		newOffset++;
	}
	ErrorIf(newOffset == dataSize - 1, ERROR_MORE_DATA);
	ErrorIf(0x0A != data[newOffset + 1], ERROR_BAD_FORMAT);
	
	// set HTTP response status line

	data[newOffset] = 0; // zero-terminate the reason phrase to reuse it without copying

	IHttpResponse* response = context->GetHttpContext()->GetResponse();
	response->Clear();
	response->SetStatus(statusCode, data + offset, subStatusCode);
	
	// adjust buffers

	context->SetParsingOffset(context->GetParsingOffset() + newOffset + 2);

	return S_OK;
Error:

	return hr;
}
Ejemplo n.º 14
0
HRESULT CHttpProtocol::ParseResponseStatusLine(CNodeHttpStoredContext* context)
{
	HRESULT hr;

	char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
	DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
	DWORD offset = 0;	
	USHORT major, minor;
	DWORD count, newOffset;
	char tmp[256];
	char* tmp1;
	USHORT statusCode, subStatusCode = 0;

	// HTTP-Version SP

	context->GetHttpContext()->GetRequest()->GetHttpVersion(&major, &minor);
	if (1 == major && 1 == minor)
	{
		tmp1 = "HTTP/1.1 ";
		count = 9;
	}
	else if (1 == major && 0 == minor)
	{
		tmp1 = "HTTP/1.0 ";
		count = 9;
	}
	else
	{
		sprintf(tmp, "HTTP/%d.%d ", major, minor);
		count = strlen(tmp);
		tmp1 = tmp;
	}

	ErrorIf(count >= dataSize, ERROR_MORE_DATA);
	ErrorIf(0 != memcmp(tmp1, data, 5), ERROR_BAD_FORMAT);
	offset += count;

	// Status-Code[.Sub-Status-Code] SP

	statusCode = 0;
	while (offset < dataSize && data[offset] >= '0' && data[offset] <= '9')
	{
		statusCode = statusCode * 10 + data[offset++] - '0';
	}
	ErrorIf(offset == dataSize, ERROR_MORE_DATA);

	if ('.' == data[offset])
	{
		// Sub-Status-Code

		offset++;		

		while (offset < dataSize && data[offset] >= '0' && data[offset] <= '9')
		{
			subStatusCode = subStatusCode * 10 + data[offset++] - '0';
		}
		ErrorIf(offset == dataSize, ERROR_MORE_DATA);
	}

	ErrorIf(' ' != data[offset], ERROR_BAD_FORMAT);
	offset++;

	// Determine whether to expect response entity body
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4

	if (statusCode == 101) 
	{
		CheckError(context->SetupUpgrade());
	}
	else if (statusCode >= 100 && statusCode < 200
		|| statusCode == 204
		|| statusCode == 304)
	{
		context->SetExpectResponseBody(FALSE);
	}

	// Reason-Phrase CRLF

	newOffset = offset;
	while (newOffset < (dataSize - 1) && data[newOffset] != 0x0D)
	{
		newOffset++;
	}
	ErrorIf(newOffset == dataSize - 1, ERROR_MORE_DATA);
	ErrorIf(0x0A != data[newOffset + 1], ERROR_BAD_FORMAT);
	
	// set HTTP response status line

	data[newOffset] = 0; // zero-terminate the reason phrase to reuse it without copying

	IHttpResponse* response = context->GetHttpContext()->GetResponse();
	response->SetStatus(statusCode, data + offset, subStatusCode);
	
	// adjust buffers

	context->SetParsingOffset(context->GetParsingOffset() + newOffset + 2);

	return S_OK;
Error:

	if (ERROR_MORE_DATA != hr)
	{
		context->GetNodeApplication()->GetApplicationManager()->GetEventProvider()->Log(
			L"iisnode failed to parse response status line", WINEVENT_LEVEL_ERROR, context->GetActivityId());
	}

	return hr;
}
Ejemplo n.º 15
0
	REQUEST_NOTIFICATION_STATUS OnSendResponse( IN IHttpContext * pHttpContext, IN ISendResponseProvider * pProvider )
	{
			UNREFERENCED_PARAMETER( pProvider );

			pMgr        = NULL;
			pParentElem = NULL;
			pElemColl   = NULL;
			pElem       = NULL;
			pElemProps  = NULL;
			pElemProp   = NULL;

			hr                   = S_OK;
			bstrConfigCommitPath = SysAllocString( L"MACHINE/WEBROOT/APPHOST" );
			bstrSectionName      = SysAllocString( L"system.webServer/stripHeaders" );
			bstrPropertyName     = SysAllocString( L"name" );
			dwElementCount       = 0;
			vtPropertyName.vt            = VT_BSTR;
			vtPropertyName.bstrVal       = bstrPropertyName;

			// Initialize
			hr = CoInitializeEx( NULL, COINIT_MULTITHREADED );
			if ( FAILED( hr ) )
			{
				// Set the error status.
				pProvider->SetErrorStatus( hr );
				// cleanup
				cleanup();
				// End additional processing.
				return RQ_NOTIFICATION_FINISH_REQUEST;
			}

			// Create
			hr = CoCreateInstance( __uuidof( AppHostAdminManager ), NULL, 
					CLSCTX_INPROC_SERVER,
					__uuidof( IAppHostAdminManager ), (void**) &pMgr );
			if( FAILED( hr ) )
			{
				pProvider->SetErrorStatus( hr );
				cleanup();
				return RQ_NOTIFICATION_FINISH_REQUEST;
			}

			// Get the admin section
			hr = pMgr->GetAdminSection( bstrSectionName, bstrConfigCommitPath, &pParentElem );
			if ( FAILED( hr ) || ( &pParentElem == NULL ) )
			{
				pProvider->SetErrorStatus( hr );
				cleanup();
				return RQ_NOTIFICATION_FINISH_REQUEST;
			}


			// Get the site collection
			hr = pParentElem->get_Collection( &pElemColl );
			if ( FAILED ( hr ) || ( &pElemColl == NULL ) )
			{
				pProvider->SetErrorStatus( hr );
				cleanup();
				return RQ_NOTIFICATION_FINISH_REQUEST;
			}

			// Get the elements
			hr = pElemColl->get_Count( &dwElementCount );
			for ( USHORT i = 0; i < dwElementCount; i++ )
			{
				VARIANT vtItemIndex;
				vtItemIndex.vt = VT_I2;
				vtItemIndex.iVal = i;

				// Add a new section group
				hr = pElemColl->get_Item( vtItemIndex, &pElem );
				if ( FAILED( hr ) || ( &pElem == NULL ) )
				{
					pProvider->SetErrorStatus( hr );
					cleanup();
					return RQ_NOTIFICATION_FINISH_REQUEST;
				}

				// Get the child elements
				hr = pElem->get_Properties( &pElemProps );
				if ( FAILED( hr ) || ( &pElemProps == NULL ) )
				{
					pProvider->SetErrorStatus( hr );
					cleanup();
					return RQ_NOTIFICATION_FINISH_REQUEST;
				}

				hr = pElemProps->get_Item( vtPropertyName, &pElemProp );
				if ( FAILED( hr ) || ( pElemProp == NULL ) )
				{
					pProvider->SetErrorStatus( hr );
					cleanup();
					return RQ_NOTIFICATION_FINISH_REQUEST;
				}

				hr = pElemProp->get_Value( &vtValue );
				if ( FAILED( hr ) )
				{
					pProvider->SetErrorStatus( hr );
					cleanup();
					return RQ_NOTIFICATION_FINISH_REQUEST;
				}

				// Retrieve a pointer to the response.
				IHttpResponse * pHttpResponse = pHttpContext->GetResponse();

				// Test for an error.
				if ( pHttpResponse != NULL )
				{
					// convert bstr to string in order to delete header
					_bstr_t header( vtValue.bstrVal );

					// delete header
					hr = pHttpResponse->DeleteHeader( (char *)header );

					// Test for an error.
					if ( FAILED( hr ) )
					{
						// Set the error status.
						pProvider->SetErrorStatus( hr );
						// cleanup
						cleanup();
						// End additional processing.
						return RQ_NOTIFICATION_FINISH_REQUEST;
					}
				}

				// loop_cleanup
				if ( pElem != NULL )
				{
					pElem->Release(); 
					pElem = NULL;
				}
			}

			cleanup();
			// Return processing to the pipeline.
			return RQ_NOTIFICATION_CONTINUE;
		}
Ejemplo n.º 16
0
HRESULT CHttpProtocol::ParseResponseHeaders(CNodeHttpStoredContext* context)
{
	HRESULT hr;
	char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
	DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
	DWORD offset = 0;	
	DWORD nameEndOffset, valueEndOffset;
	IHttpResponse* response = context->GetHttpContext()->GetResponse();
	BOOL needConnectionHeaderFixup = FALSE;

	while (offset < (dataSize - 1) && data[offset] != 0x0D)
	{
		// header name

		nameEndOffset = offset;
		while (nameEndOffset < dataSize && data[nameEndOffset] != ':')
		{
			nameEndOffset++;
		}
		ErrorIf(nameEndOffset == dataSize, ERROR_MORE_DATA);

		// header value

		valueEndOffset = nameEndOffset + 1;
		while (valueEndOffset < (dataSize - 1) && data[valueEndOffset] != 0x0D)
		{
			valueEndOffset++;
		}
		ErrorIf(valueEndOffset >= dataSize - 1, ERROR_MORE_DATA);
		ErrorIf(0x0A != data[valueEndOffset + 1], ERROR_BAD_FORMAT);

		// set header on response
		
		data[nameEndOffset] = 0; // zero-terminate name to reuse without copying		

		// Skip the Connection header because it relates to the iisnode <-> node.exe communication over named pipes,
		// unless this is a 101 response to HTTP Upgrade request, in which case this is used to inform HTTP.SYS to keep the
		// connection open.
		if (context->GetIsUpgrade() || 0 != strcmpi("Connection", data + offset))
		{
			data[valueEndOffset] = 0; // zero-terminate header value because this is what IHttpResponse::SetHeader expects

			// skip over ':'
			nameEndOffset++; 

			// skip over leading whitespace in value		
			while (*(data + nameEndOffset) == ' ') // data is already zero-terminated, so this loop has sentinel value
				nameEndOffset++;

			if (context->GetIsUpgrade() && 0 == strcmpi("Connection", data + offset))
			{
				// Add the Connection header under a custom name to force it to be added to unknown headers collection.
				// Header name will subsequently be fixed up back to Connection. 
				// The end result is that the Connection header ends up in the unknown headers collection rather then
				// known headers collection where it would get ignored by http.sys.
				
				CheckError(response->SetHeader("x-iisnode-connection", data + nameEndOffset, valueEndOffset - nameEndOffset, FALSE));
				needConnectionHeaderFixup = TRUE;
			}
			else
			{
				CheckError(response->SetHeader(data + offset, data + nameEndOffset, valueEndOffset - nameEndOffset, FALSE));
			}
		}
		else if ((valueEndOffset - nameEndOffset) >= 5 && 0 == memcmp((void*)(data + valueEndOffset - 5), "close", 5))
		{
			context->SetCloseConnection(TRUE);
		}

		// adjust offsets
		
		context->SetParsingOffset(context->GetParsingOffset() + valueEndOffset - offset + 2);
		offset = valueEndOffset + 2;
	}

	ErrorIf(offset >= dataSize - 1, ERROR_MORE_DATA);
	ErrorIf(0x0A != data[offset + 1], ERROR_BAD_FORMAT);

	context->SetParsingOffset(context->GetParsingOffset() + 2);

	if (needConnectionHeaderFixup)
	{
		HTTP_RESPONSE* rawResponse = response->GetRawHttpResponse();
		for (int i = 0; i < response->GetRawHttpResponse()->Headers.UnknownHeaderCount; i++)
		{
			if (20 == rawResponse->Headers.pUnknownHeaders[i].NameLength 
				&& 0 == _strnicmp("x-iisnode-connection", rawResponse->Headers.pUnknownHeaders[i].pName, 20))
			{
				rawResponse->Headers.pUnknownHeaders[i].NameLength = 10;
				rawResponse->Headers.pUnknownHeaders[i].pName = "Connection";
				break;
			}
		}
	}

	return S_OK;
Error:
	return hr;
}
Ejemplo n.º 17
0
REQUEST_NOTIFICATION_STATUS
CMyHttpModule::OnSendResponse(
    IN IHttpContext * pHttpContext,
    IN ISendResponseProvider * pResponseProvider
)
{
	REQUEST_STORED_CONTEXT *rsc = NULL;

	rsc = (REQUEST_STORED_CONTEXT *)pHttpContext->GetModuleContextContainer()->GetModuleContext(g_pModuleContext);

	EnterCriticalSection(&m_csLock);

	// here we must check if response body processing is enabled
	//
	if(rsc == NULL || rsc->m_pRequestRec == NULL || rsc->m_pResponseBuffer != NULL || !modsecIsResponseBodyAccessEnabled(rsc->m_pRequestRec))
	{
		goto Exit;
	}

    HRESULT hr = S_OK;
	IHttpResponse *pHttpResponse = NULL;
    HTTP_RESPONSE *pRawHttpResponse = NULL;
    HTTP_BYTE_RANGE *pFileByteRange = NULL;
    HTTP_DATA_CHUNK *pSourceDataChunk = NULL;
    LARGE_INTEGER  lFileSize;
    REQUEST_NOTIFICATION_STATUS ret = RQ_NOTIFICATION_CONTINUE;
	ULONGLONG ulTotalLength = 0;
	DWORD c;
	request_rec *r = rsc->m_pRequestRec;

	pHttpResponse = pHttpContext->GetResponse();
	pRawHttpResponse = pHttpResponse->GetRawHttpResponse();

	// here we must add handling of chunked response
	// apparently IIS 7 calls this handler once per chunk
	// see: http://stackoverflow.com/questions/4385249/how-to-buffer-and-process-chunked-data-before-sending-headers-in-iis7-native-mod

	if(pRawHttpResponse->EntityChunkCount == 0)
		goto Exit;

	// here we must transfer response headers
	//
	USHORT ctcch = 0;
	char *ct = (char *)pHttpResponse->GetHeader(HttpHeaderContentType, &ctcch);
	char *ctz = ZeroTerminate(ct, ctcch, r->pool);

	// assume HTML if content type not set
	// without this output filter would not buffer response and processing would hang
	//
	if(ctz[0] == 0)
		ctz = "text/html";

	r->content_type = ctz;

#define _TRANSHEADER(id,str) if(pRawHttpResponse->Headers.KnownHeaders[id].pRawValue != NULL) \
	{\
		apr_table_setn(r->headers_out, str, \
			ZeroTerminate(pRawHttpResponse->Headers.KnownHeaders[id].pRawValue, pRawHttpResponse->Headers.KnownHeaders[id].RawValueLength, r->pool)); \
	}

    _TRANSHEADER(HttpHeaderCacheControl, "Cache-Control");
    _TRANSHEADER(HttpHeaderConnection, "Connection");
    _TRANSHEADER(HttpHeaderDate, "Date");
    _TRANSHEADER(HttpHeaderKeepAlive, "Keep-Alive");             
    _TRANSHEADER(HttpHeaderPragma, "Pragma");                
    _TRANSHEADER(HttpHeaderTrailer, "Trailer");               
    _TRANSHEADER(HttpHeaderTransferEncoding, "Transfer-Encoding");      
    _TRANSHEADER(HttpHeaderUpgrade, "Upgrade");               
    _TRANSHEADER(HttpHeaderVia, "Via");                   
    _TRANSHEADER(HttpHeaderWarning, "Warning");               
    _TRANSHEADER(HttpHeaderAllow, "Allow");                 
    _TRANSHEADER(HttpHeaderContentLength, "Content-Length");         
    _TRANSHEADER(HttpHeaderContentType, "Content-Type");           
    _TRANSHEADER(HttpHeaderContentEncoding, "Content-Encoding");       
    _TRANSHEADER(HttpHeaderContentLanguage, "Content-Language");       
    _TRANSHEADER(HttpHeaderContentLocation, "Content-Location");       
    _TRANSHEADER(HttpHeaderContentMd5, "Content-Md5");            
    _TRANSHEADER(HttpHeaderContentRange, "Content-Range");          
    _TRANSHEADER(HttpHeaderExpires, "Expires");               
    _TRANSHEADER(HttpHeaderLastModified, "Last-Modified");          
	_TRANSHEADER(HttpHeaderAcceptRanges, "Accept-Ranges");
    _TRANSHEADER(HttpHeaderAge, "Age");                   
    _TRANSHEADER(HttpHeaderEtag, "Etag");                  
    _TRANSHEADER(HttpHeaderLocation, "Location");              
    _TRANSHEADER(HttpHeaderProxyAuthenticate, "Proxy-Authenticate");     
    _TRANSHEADER(HttpHeaderRetryAfter, "Retry-After");            
    _TRANSHEADER(HttpHeaderServer, "Server");                
    _TRANSHEADER(HttpHeaderSetCookie, "Set-Cookie");             
    _TRANSHEADER(HttpHeaderVary, "Vary");                  
    _TRANSHEADER(HttpHeaderWwwAuthenticate, "Www-Authenticate");

#undef	_TRANSHEADER

	for(int i = 0; i < pRawHttpResponse->Headers.UnknownHeaderCount; i++)
	{
		apr_table_setn(r->headers_out, 
			ZeroTerminate(pRawHttpResponse->Headers.pUnknownHeaders[i].pName, pRawHttpResponse->Headers.pUnknownHeaders[i].NameLength, r->pool), 
			ZeroTerminate(pRawHttpResponse->Headers.pUnknownHeaders[i].pRawValue, pRawHttpResponse->Headers.pUnknownHeaders[i].RawValueLength, r->pool));
	}

	r->content_encoding = apr_table_get(r->headers_out, "Content-Encoding");
	//r->content_type = apr_table_get(r->headers_out, "Content-Type");		-- already set above

	const char *lng = apr_table_get(r->headers_out, "Content-Languages");

	if(lng != NULL)
	{
		r->content_languages = apr_array_make(r->pool, 1, sizeof(const char *));

		*(const char **)apr_array_push(r->content_languages) = lng;
	}

	// Disable kernel caching for this response
	// Probably we don't have to do it for ModSecurity

    //pHttpContext->GetResponse()->DisableKernelCache(
    //        IISCacheEvents::HTTPSYS_CACHEABLE::HANDLER_HTTPSYS_UNFRIENDLY);

    for(c = 0; c < pRawHttpResponse->EntityChunkCount; c++ )
    {
        pSourceDataChunk = &pRawHttpResponse->pEntityChunks[ c ];

        switch( pSourceDataChunk->DataChunkType )
        {
            case HttpDataChunkFromMemory:
                ulTotalLength += pSourceDataChunk->FromMemory.BufferLength;
                break;
            case HttpDataChunkFromFileHandle:
                pFileByteRange = &pSourceDataChunk->FromFileHandle.ByteRange;
                //
                // File chunks may contain by ranges with unspecified length 
                // (HTTP_BYTE_RANGE_TO_EOF).  In order to send parts of such a chunk, 
                // its necessary to know when the chunk is finished, and
                // we need to move to the next chunk.
                //              
                if ( pFileByteRange->Length.QuadPart == HTTP_BYTE_RANGE_TO_EOF)
                {
                    if ( GetFileType( pSourceDataChunk->FromFileHandle.FileHandle ) == 
                                    FILE_TYPE_DISK )
                    {
                        if ( !GetFileSizeEx( pSourceDataChunk->FromFileHandle.FileHandle,
                                             &lFileSize ) )
                        {
                            DWORD dwError = GetLastError();
                            hr = HRESULT_FROM_WIN32(dwError);
                            goto Finished;
                        }

                        // put the resolved file length in the chunk, replacing 
                        // HTTP_BYTE_RANGE_TO_EOF
                        pFileByteRange->Length.QuadPart = 
                              lFileSize.QuadPart - pFileByteRange->StartingOffset.QuadPart;
                    }
                    else
                    {
                        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
                        goto Finished;                        
                    }
                }  

                ulTotalLength += pFileByteRange->Length.QuadPart;
                break;
            default:
                // TBD: consider implementing HttpDataChunkFromFragmentCache, 
                // and HttpDataChunkFromFragmentCacheEx
                hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
                goto Finished;                        
        }
    }

	rsc->m_pResponseBuffer = (char *)apr_palloc(rsc->m_pRequestRec->pool, ulTotalLength);

	ulTotalLength = 0;

    for(c = 0; c < pRawHttpResponse->EntityChunkCount; c++ )
    {
        pSourceDataChunk = &pRawHttpResponse->pEntityChunks[ c ];

        switch( pSourceDataChunk->DataChunkType )
        {
            case HttpDataChunkFromMemory:
				memcpy(rsc->m_pResponseBuffer + ulTotalLength, pSourceDataChunk->FromMemory.pBuffer, pSourceDataChunk->FromMemory.BufferLength);
                ulTotalLength += pSourceDataChunk->FromMemory.BufferLength;
                break;
            case HttpDataChunkFromFileHandle:
                pFileByteRange = &pSourceDataChunk->FromFileHandle.ByteRange;

				if(ReadFileChunk(pSourceDataChunk, rsc->m_pResponseBuffer + ulTotalLength) != S_OK)
				{
			        DWORD dwErr = GetLastError();

					hr = HRESULT_FROM_WIN32(dwErr);
	                goto Finished;
				}

                ulTotalLength += pFileByteRange->Length.QuadPart;
                break;
            default:
                // TBD: consider implementing HttpDataChunkFromFragmentCache, 
                // and HttpDataChunkFromFragmentCacheEx
                hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
                goto Finished;                        
        }
    }

	rsc->m_pResponseLength = ulTotalLength;

	//
    // If there's no content-length set, we need to set it to avoid chunked transfer mode
    // We can only do it if there is it's the only response to be sent.
    //

    DWORD dwFlags = pResponseProvider->GetFlags();

	if (pResponseProvider->GetHeadersBeingSent() && 
         (dwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0 &&
         pHttpContext->GetResponse()->GetHeader(HttpHeaderContentLength) == NULL)    
    {
        CHAR szLength[21]; //Max length for a 64 bit int is 20

         ZeroMemory(szLength, sizeof(szLength));

         hr = StringCchPrintfA(
                    szLength, 
                    sizeof(szLength) / sizeof(CHAR) - 1, "%d", 
                    ulTotalLength);

        if(FAILED(hr))
        {
            goto Finished;      
        }

         hr = pHttpContext->GetResponse()->SetHeader(
                    HttpHeaderContentLength, 
                    szLength, 
                    (USHORT)strlen(szLength),
                    TRUE);

        if(FAILED(hr))
        {
            goto Finished;      
        }
    }

Finished:

	int status = modsecProcessResponse(rsc->m_pRequestRec);

	// the logic here is temporary, needs clarification
	//
	if(status != 0 && status != -1)
	{
		pHttpContext->GetResponse()->Clear();
		pHttpContext->GetResponse()->SetStatus(status, "ModSecurity Action");
		pHttpContext->SetRequestHandled();

		rsc->FinishRequest();

		LeaveCriticalSection(&m_csLock);
		
		return RQ_NOTIFICATION_FINISH_REQUEST;
	}
Exit:
	// temporary hack, in reality OnSendRequest theoretically could possibly come before OnEndRequest
	//
	if(rsc != NULL)
		rsc->FinishRequest();

	LeaveCriticalSection(&m_csLock);
		
	return RQ_NOTIFICATION_CONTINUE;
}
Ejemplo n.º 18
0
HRESULT CHttpProtocol::ParseResponseHeaders(CNodeHttpStoredContext* context)
{
	HRESULT hr;
	char* data = (char*)context->GetBuffer() + context->GetParsingOffset();
	DWORD dataSize = context->GetDataSize() - context->GetParsingOffset();
	DWORD offset = 0;	
	DWORD nameEndOffset, valueEndOffset;
	IHttpResponse* response = context->GetHttpContext()->GetResponse();

	while (offset < (dataSize - 1) && data[offset] != 0x0D)
	{
		// header name

		nameEndOffset = offset;
		while (nameEndOffset < dataSize && data[nameEndOffset] != ':')
		{
			nameEndOffset++;
		}
		ErrorIf(nameEndOffset == dataSize, ERROR_MORE_DATA);

		// header value

		valueEndOffset = nameEndOffset + 1;
		while (valueEndOffset < (dataSize - 1) && data[valueEndOffset] != 0x0D)
		{
			valueEndOffset++;
		}
		ErrorIf(valueEndOffset >= dataSize - 1, ERROR_MORE_DATA);
		ErrorIf(0x0A != data[valueEndOffset + 1], ERROR_BAD_FORMAT);

		// set header on response
		
		data[nameEndOffset] = 0; // zero-terminate name to reuse without copying		

		// skip the connection header because it relates to the iisnode <-> node.exe communication over named pipes 
		if (0 != strcmpi("Connection", data + offset))
		{
			data[valueEndOffset] = 0; // zero-terminate header value because this is what IHttpResponse::SetHeader expects

			// skip over ':'
			nameEndOffset++; 

			// skip over leading whitespace in value		
			while (*(data + nameEndOffset) == ' ') // data is already zero-terminated, so this loop has sentinel value
				nameEndOffset++;

			CheckError(response->SetHeader(data + offset, data + nameEndOffset, valueEndOffset - nameEndOffset, TRUE));
		}
		else if ((valueEndOffset - nameEndOffset) >= 5 && 0 == memcmp((void*)(data + valueEndOffset - 5), "close", 5))
		{
			context->SetCloseConnection(TRUE);
		}

		// adjust offsets
		
		context->SetParsingOffset(context->GetParsingOffset() + valueEndOffset - offset + 2);
		offset = valueEndOffset + 2;
	}
	ErrorIf(offset >= dataSize - 1, ERROR_MORE_DATA);
	ErrorIf(0x0A != data[offset + 1], ERROR_BAD_FORMAT);

	context->SetParsingOffset(context->GetParsingOffset() + 2);

	return S_OK;
Error:
	return hr;
}