private List <GCHandle> SerializeHeaders(ref Interop.HttpApi.HTTP_RESPONSE_HEADERS headers, bool isWebSocketHandshake) { Interop.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; List <GCHandle> pinnedHeaders; GCHandle gcHandle; if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "SerializeHeaders(HTTP_RESPONSE_HEADERS)"); } if (Headers.Count == 0) { return(null); } string headerName; string headerValue; int lookup; byte[] bytes = null; pinnedHeaders = new List <GCHandle>(); //--------------------------------------------------- // The Set-Cookie headers are being merged into one. // There are two issues here. // 1. When Set-Cookie headers are set through SetCookie method on the ListenerResponse, // there is code in the SetCookie method and the methods it calls to flatten the Set-Cookie // values. This blindly concatenates the cookies with a comma delimiter. There could be // a cookie value that contains comma, but we don't escape it with %XX value // // As an alternative users can add the Set-Cookie header through the AddHeader method // like ListenerResponse.Headers.Add("name", "value") // That way they can add multiple headers - AND They can format the value like they want it. // // 2. Now that the header collection contains multiple Set-Cookie name, value pairs // you would think the problem would go away. However here is an interesting thing. // For NameValueCollection, when you add // "Set-Cookie", "value1" // "Set-Cookie", "value2" // The NameValueCollection.Count == 1. Because there is only one key // NameValueCollection.Get("Set-Cookie") would conviniently take these two valuess // concatenate them with a comma like // value1,value2. // In order to get individual values, you need to use // string[] values = NameValueCollection.GetValues("Set-Cookie"); // // ------------------------------------------------------------- // So here is the proposed fix here. // We must first to loop through all the NameValueCollection keys // and if the name is a unknown header, we must compute the number of // values it has. Then, we should allocate that many unknown header array // elements. // // Note that a part of the fix here is to treat Set-Cookie as an unknown header // // //----------------------------------------------------------- int numUnknownHeaders = 0; for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; //See if this is an unknown header lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); //Treat Set-Cookie as well as Connection header in Websocket mode as unknown if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (lookup == -1) { string[] headerValues = Headers.GetValues(index); numUnknownHeaders += headerValues.Length; } } try { fixed(Interop.HttpApi.HTTP_KNOWN_HEADER *pKnownHeaders = &headers.KnownHeaders) { for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; headerValue = Headers.Get(index) as string; lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"index={index},headers.count={Headers.Count},headerName:{headerName},lookup:{lookup} headerValue:{headerValue}"); } if (lookup == -1) { if (unknownHeaders == null) { unknownHeaders = new Interop.HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); headers.pUnknownHeaders = (Interop.HttpApi.HTTP_UNKNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); } //---------------------------------------- //FOR UNKNOWN HEADERS //ALLOW MULTIPLE HEADERS to be added //--------------------------------------- string[] headerValues = Headers.GetValues(index); for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++) { //Add Name bytes = new byte[WebHeaderEncoding.GetByteCount(headerName)]; unknownHeaders[headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerName, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pName = (sbyte *)gcHandle.AddrOfPinnedObject(); //Add Value headerValue = headerValues[headerValueIndex]; bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; unknownHeaders[headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); headers.UnknownHeaderCount++; if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "UnknownHeaderCount:" + headers.UnknownHeaderCount); } } } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"HttpResponseHeader[{lookup}]:{((HttpResponseHeader)lookup)} headerValue:{headerValue}"); } if (headerValue != null) { bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pKnownHeaders[lookup].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"pRawValue:{((IntPtr)(pKnownHeaders[lookup].pRawValue))} RawValueLength:{pKnownHeaders[lookup].RawValueLength} lookup: {lookup}"); } } } } } } catch { FreePinnedHeaders(pinnedHeaders); throw; } return(pinnedHeaders); }
private void SendError(ulong requestId, HttpStatusCode httpStatusCode, ArrayList challenges) { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"RequestId: {requestId}"); Interop.HttpApi.HTTP_RESPONSE httpResponse = new Interop.HttpApi.HTTP_RESPONSE(); httpResponse.Version = new Interop.HttpApi.HTTP_VERSION(); httpResponse.Version.MajorVersion = (ushort)1; httpResponse.Version.MinorVersion = (ushort)1; httpResponse.StatusCode = (ushort)httpStatusCode; string statusDescription = HttpStatusDescription.Get(httpStatusCode); uint DataWritten = 0; uint statusCode; byte[] byteReason = Encoding.Default.GetBytes(statusDescription); fixed (byte* pReason = byteReason) { httpResponse.pReason = (sbyte*)pReason; httpResponse.ReasonLength = (ushort)byteReason.Length; byte[] byteContentLength = Encoding.Default.GetBytes("0"); fixed (byte* pContentLength = byteContentLength) { (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].pRawValue = (sbyte*)pContentLength; (&httpResponse.Headers.KnownHeaders)[(int)HttpResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length; httpResponse.Headers.UnknownHeaderCount = checked((ushort)(challenges == null ? 0 : challenges.Count)); GCHandle[] challengeHandles = null; Interop.HttpApi.HTTP_UNKNOWN_HEADER[] headersArray = null; GCHandle headersArrayHandle = new GCHandle(); GCHandle wwwAuthenticateHandle = new GCHandle(); if (httpResponse.Headers.UnknownHeaderCount > 0) { challengeHandles = new GCHandle[httpResponse.Headers.UnknownHeaderCount]; headersArray = new Interop.HttpApi.HTTP_UNKNOWN_HEADER[httpResponse.Headers.UnknownHeaderCount]; } try { if (httpResponse.Headers.UnknownHeaderCount > 0) { headersArrayHandle = GCHandle.Alloc(headersArray, GCHandleType.Pinned); httpResponse.Headers.pUnknownHeaders = (Interop.HttpApi.HTTP_UNKNOWN_HEADER*)Marshal.UnsafeAddrOfPinnedArrayElement(headersArray, 0); wwwAuthenticateHandle = GCHandle.Alloc(s_WwwAuthenticateBytes, GCHandleType.Pinned); sbyte* wwwAuthenticate = (sbyte*)Marshal.UnsafeAddrOfPinnedArrayElement(s_WwwAuthenticateBytes, 0); for (int i = 0; i < challengeHandles.Length; i++) { byte[] byteChallenge = Encoding.Default.GetBytes((string)challenges[i]); challengeHandles[i] = GCHandle.Alloc(byteChallenge, GCHandleType.Pinned); headersArray[i].pName = wwwAuthenticate; headersArray[i].NameLength = (ushort)s_WwwAuthenticateBytes.Length; headersArray[i].pRawValue = (sbyte*)Marshal.UnsafeAddrOfPinnedArrayElement(byteChallenge, 0); headersArray[i].RawValueLength = checked((ushort)byteChallenge.Length); } } if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendHtthttpResponse"); statusCode = Interop.HttpApi.HttpSendHttpResponse( _requestQueueHandle, requestId, 0, &httpResponse, null, &DataWritten, SafeLocalAllocHandle.Zero, 0, null, null); } finally { if (headersArrayHandle.IsAllocated) { headersArrayHandle.Free(); } if (wwwAuthenticateHandle.IsAllocated) { wwwAuthenticateHandle.Free(); } if (challengeHandles != null) { for (int i = 0; i < challengeHandles.Length; i++) { if (challengeHandles[i].IsAllocated) { challengeHandles[i].Free(); } } } } } } if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendHttpResponse returned:" + statusCode); if (statusCode != Interop.HttpApi.ERROR_SUCCESS) { // if we fail to send a 401 something's seriously wrong, abort the request if (NetEventSource.IsEnabled) NetEventSource.Info(this, "SendUnauthorized returned:" + statusCode); HttpListenerContext.CancelRequest(_requestQueueHandle, requestId); } }
private List<GCHandle> SerializeHeaders(ref Interop.HttpApi.HTTP_RESPONSE_HEADERS headers, bool isWebSocketHandshake) { Interop.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; List<GCHandle> pinnedHeaders; GCHandle gcHandle; if (NetEventSource.IsEnabled) NetEventSource.Info(this, "SerializeHeaders(HTTP_RESPONSE_HEADERS)"); if (Headers.Count == 0) { return null; } string headerName; string headerValue; int lookup; byte[] bytes = null; pinnedHeaders = new List<GCHandle>(); //--------------------------------------------------- // The Set-Cookie headers are being merged into one. // There are two issues here. // 1. When Set-Cookie headers are set through SetCookie method on the ListenerResponse, // there is code in the SetCookie method and the methods it calls to flatten the Set-Cookie // values. This blindly concatenates the cookies with a comma delimiter. There could be // a cookie value that contains comma, but we don't escape it with %XX value // // As an alternative users can add the Set-Cookie header through the AddHeader method // like ListenerResponse.Headers.Add("name", "value") // That way they can add multiple headers - AND They can format the value like they want it. // // 2. Now that the header collection contains multiple Set-Cookie name, value pairs // you would think the problem would go away. However here is an interesting thing. // For NameValueCollection, when you add // "Set-Cookie", "value1" // "Set-Cookie", "value2" // The NameValueCollection.Count == 1. Because there is only one key // NameValueCollection.Get("Set-Cookie") would conviniently take these two valuess // concatenate them with a comma like // value1,value2. // In order to get individual values, you need to use // string[] values = NameValueCollection.GetValues("Set-Cookie"); // // ------------------------------------------------------------- // So here is the proposed fix here. // We must first to loop through all the NameValueCollection keys // and if the name is a unknown header, we must compute the number of // values it has. Then, we should allocate that many unknown header array // elements. // // Note that a part of the fix here is to treat Set-Cookie as an unknown header // // //----------------------------------------------------------- int numUnknownHeaders = 0; for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; //See if this is an unknown header lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); //Treat Set-Cookie as well as Connection header in Websocket mode as unknown if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (lookup == -1) { string[] headerValues = Headers.GetValues(index); numUnknownHeaders += headerValues.Length; } } try { fixed (Interop.HttpApi.HTTP_KNOWN_HEADER* pKnownHeaders = &headers.KnownHeaders) { for (int index = 0; index < Headers.Count; index++) { headerName = Headers.GetKey(index) as string; headerValue = Headers.Get(index) as string; lookup = Interop.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); if (lookup == (int)HttpResponseHeader.SetCookie || isWebSocketHandshake && lookup == (int)HttpResponseHeader.Connection) { lookup = -1; } if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"index={index},headers.count={Headers.Count},headerName:{headerName},lookup:{lookup} headerValue:{headerValue}"); if (lookup == -1) { if (unknownHeaders == null) { unknownHeaders = new Interop.HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); headers.pUnknownHeaders = (Interop.HttpApi.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject(); } //---------------------------------------- //FOR UNKNOWN HEADERS //ALLOW MULTIPLE HEADERS to be added //--------------------------------------- string[] headerValues = Headers.GetValues(index); for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++) { //Add Name bytes = new byte[WebHeaderEncoding.GetByteCount(headerName)]; unknownHeaders[headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerName, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pName = (sbyte*)gcHandle.AddrOfPinnedObject(); //Add Value headerValue = headerValues[headerValueIndex]; bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; unknownHeaders[headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[headers.UnknownHeaderCount].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject(); headers.UnknownHeaderCount++; if (NetEventSource.IsEnabled) NetEventSource.Info(this, "UnknownHeaderCount:" + headers.UnknownHeaderCount); } } else { if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HttpResponseHeader[{lookup}]:{((HttpResponseHeader)lookup)} headerValue:{headerValue}"); if (headerValue != null) { bytes = new byte[WebHeaderEncoding.GetByteCount(headerValue)]; pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length; WebHeaderEncoding.GetBytes(headerValue, 0, bytes.Length, bytes, 0); gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pKnownHeaders[lookup].pRawValue = (sbyte*)gcHandle.AddrOfPinnedObject(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"pRawValue:{((IntPtr)(pKnownHeaders[lookup].pRawValue))} RawValueLength:{pKnownHeaders[lookup].RawValueLength} lookup: {lookup}"); } } } } } } catch { FreePinnedHeaders(pinnedHeaders); throw; } return pinnedHeaders; }