private List <GCHandle> SerializeHeaders(bool isOpaqueUpgrade) { Headers.IsReadOnly = true; // Prohibit further modifications. HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; List <GCHandle> pinnedHeaders; GCHandle gcHandle; /* * // here we would check for BoundaryType.Raw, in this case we wouldn't need to do anything * if (m_BoundaryType==BoundaryType.Raw) { * return null; * } */ if (Headers.Count == 0) { return(null); } string headerName; string headerValue; int lookup; byte[] bytes = null; pinnedHeaders = new List <GCHandle>(); int numUnknownHeaders = 0; int numKnownMultiHeaders = 0; foreach (var headerPair in Headers) { if (headerPair.Value.Count == 0) { continue; } // See if this is an unknown header lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key); // Http.Sys doesn't let us send the Connection: Upgrade header as a Known header. if (lookup == -1 || (isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { numUnknownHeaders += headerPair.Value.Count; } else if (headerPair.Value.Count > 1) { numKnownMultiHeaders++; } // else known single-value header. } try { fixed(HttpApi.HTTP_KNOWN_HEADER *pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders) { foreach (var headerPair in Headers) { if (headerPair.Value.Count == 0) { continue; } headerName = headerPair.Key; StringValues headerValues = headerPair.Value; lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); // Http.Sys doesn't let us send the Connection: Upgrade header as a Known header. if (lookup == -1 || (isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { if (unknownHeaders == null) { unknownHeaders = new HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); _nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApi.HTTP_UNKNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); } for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++) { // Add Name bytes = HeaderEncoding.GetBytes(headerName); unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pName = (sbyte *)gcHandle.AddrOfPinnedObject(); // Add Value headerValue = headerValues[headerValueIndex] ?? string.Empty; bytes = HeaderEncoding.GetBytes(headerValue); unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[_nativeResponse.Response_V1.Headers.UnknownHeaderCount].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); _nativeResponse.Response_V1.Headers.UnknownHeaderCount++; } } else if (headerPair.Value.Count == 1) { headerValue = headerValues[0] ?? string.Empty; bytes = HeaderEncoding.GetBytes(headerValue); pKnownHeaders[lookup].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pKnownHeaders[lookup].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); } else { if (knownHeaderInfo == null) { knownHeaderInfo = new HttpApi.HTTP_RESPONSE_INFO[numKnownMultiHeaders]; gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); _nativeResponse.pResponseInfo = (HttpApi.HTTP_RESPONSE_INFO *)gcHandle.AddrOfPinnedObject(); } knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders; knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length = (uint)Marshal.SizeOf <HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>(); HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS(); header.HeaderId = (HttpApi.HTTP_RESPONSE_HEADER_ID.Enum)lookup; header.Flags = HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only. HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApi.HTTP_KNOWN_HEADER[headerValues.Count]; gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); header.KnownHeaders = (HttpApi.HTTP_KNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++) { // Add Value headerValue = headerValues[headerValueIndex] ?? string.Empty; bytes = HeaderEncoding.GetBytes(headerValue); nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); nativeHeaderValues[header.KnownHeaderCount].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); header.KnownHeaderCount++; } // This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set. gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); knownHeaderInfo[_nativeResponse.ResponseInfoCount].pInfo = (HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS *)gcHandle.AddrOfPinnedObject(); _nativeResponse.ResponseInfoCount++; } } } } catch { FreePinnedHeaders(pinnedHeaders); throw; } return(pinnedHeaders); }
private unsafe void SendError(ulong requestId, HttpStatusCode httpStatusCode, IList <string> authChallenges) { UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2 httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2(); httpResponse.Response_V1.Version = new UnsafeNclNativeMethods.HttpApi.HTTP_VERSION(); httpResponse.Response_V1.Version.MajorVersion = (ushort)1; httpResponse.Response_V1.Version.MinorVersion = (ushort)1; List <GCHandle> pinnedHeaders = null; GCHandle gcHandle; try { // Copied from the multi-value headers section of SerializeHeaders if (authChallenges != null && authChallenges.Count > 0) { pinnedHeaders = new List <GCHandle>(); UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; knownHeaderInfo = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[1]; gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); httpResponse.pResponseInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO *)gcHandle.AddrOfPinnedObject(); knownHeaderInfo[httpResponse.ResponseInfoCount].Type = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders; knownHeaderInfo[httpResponse.ResponseInfoCount].Length = (uint)Marshal.SizeOf <UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>(); UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS(); header.HeaderId = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderWwwAuthenticate; header.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // The docs say this is for www-auth only. UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[authChallenges.Count]; gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); header.KnownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); for (int headerValueIndex = 0; headerValueIndex < authChallenges.Count; headerValueIndex++) { // Add Value string headerValue = authChallenges[headerValueIndex]; byte[] bytes = HeaderEncoding.GetBytes(headerValue); nativeHeaderValues[header.KnownHeaderCount].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); nativeHeaderValues[header.KnownHeaderCount].pRawValue = (sbyte *)gcHandle.AddrOfPinnedObject(); header.KnownHeaderCount++; } // This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set. gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); knownHeaderInfo[0].pInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS *)gcHandle.AddrOfPinnedObject(); httpResponse.ResponseInfoCount = 1; } httpResponse.Response_V1.StatusCode = (ushort)httpStatusCode; string statusDescription = HttpReasonPhrase.Get(httpStatusCode); uint dataWritten = 0; uint statusCode; byte[] byteReason = HeaderEncoding.GetBytes(statusDescription); fixed(byte *pReason = byteReason) { httpResponse.Response_V1.pReason = (sbyte *)pReason; httpResponse.Response_V1.ReasonLength = (ushort)byteReason.Length; byte[] byteContentLength = new byte[] { (byte)'0' }; fixed(byte *pContentLength = byteContentLength) { (&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].pRawValue = (sbyte *)pContentLength; (&httpResponse.Response_V1.Headers.KnownHeaders)[(int)HttpSysResponseHeader.ContentLength].RawValueLength = (ushort)byteContentLength.Length; httpResponse.Response_V1.Headers.UnknownHeaderCount = 0; statusCode = UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse( _requestQueueHandle, requestId, 0, &httpResponse, null, &dataWritten, SafeLocalFree.Zero, 0, SafeNativeOverlapped.Zero, IntPtr.Zero); } } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) { // if we fail to send a 401 something's seriously wrong, abort the request RequestContext.CancelRequest(_requestQueueHandle, requestId); } } finally { if (pinnedHeaders != null) { foreach (GCHandle handle in pinnedHeaders) { if (handle.IsAllocated) { handle.Free(); } } } } }
/* * 12.3 * HttpSendHttpResponse() and HttpSendResponseEntityBody() Flag Values. * The following flags can be used on calls to HttpSendHttpResponse() and HttpSendResponseEntityBody() API calls: * #define HTTP_SEND_RESPONSE_FLAG_DISCONNECT 0x00000001 #define HTTP_SEND_RESPONSE_FLAG_MORE_DATA 0x00000002 #define HTTP_SEND_RESPONSE_FLAG_RAW_HEADER 0x00000004 #define HTTP_SEND_RESPONSE_FLAG_VALID 0x00000007 * * HTTP_SEND_RESPONSE_FLAG_DISCONNECT: * specifies that the network connection should be disconnected immediately after * sending the response, overriding the HTTP protocol's persistent connection features. * HTTP_SEND_RESPONSE_FLAG_MORE_DATA: * specifies that additional entity body data will be sent by the caller. Thus, * the last call HttpSendResponseEntityBody for a RequestId, will have this flag reset. * HTTP_SEND_RESPONSE_RAW_HEADER: * specifies that a caller of HttpSendResponseEntityBody() is intentionally omitting * a call to HttpSendHttpResponse() in order to bypass normal header processing. The * actual HTTP header will be generated by the application and sent as entity body. * This flag should be passed on the first call to HttpSendResponseEntityBody, and * not after. Thus, flag is not applicable to HttpSendHttpResponse. */ // TODO: Consider using HTTP_SEND_RESPONSE_RAW_HEADER with HttpSendResponseEntityBody instead of calling HttpSendHttpResponse. // This will give us more control of the bytes that hit the wire, including encodings, HTTP 1.0, etc.. // It may also be faster to do this work in managed code and then pass down only one buffer. // What would we loose by bypassing HttpSendHttpResponse? // // TODO: Consider using the HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA flag for most/all responses rather than just Opaque. internal unsafe uint SendHeaders(HttpApi.HTTP_DATA_CHUNK[] dataChunks, ResponseStreamAsyncResult asyncResult, HttpApi.HTTP_FLAGS flags, bool isOpaqueUpgrade) { Debug.Assert(!HasStartedSending, "HttpListenerResponse::SendHeaders()|SentHeaders is true."); _responseState = ResponseState.StartedSending; var reasonPhrase = GetReasonPhrase(StatusCode); /* * if (m_BoundaryType==BoundaryType.Raw) { * use HTTP_SEND_RESPONSE_FLAG_RAW_HEADER; * } */ uint statusCode; uint bytesSent; List <GCHandle> pinnedHeaders = SerializeHeaders(isOpaqueUpgrade); try { if (dataChunks != null) { if (pinnedHeaders == null) { pinnedHeaders = new List <GCHandle>(); } var handle = GCHandle.Alloc(dataChunks, GCHandleType.Pinned); pinnedHeaders.Add(handle); _nativeResponse.Response_V1.EntityChunkCount = (ushort)dataChunks.Length; _nativeResponse.Response_V1.pEntityChunks = (HttpApi.HTTP_DATA_CHUNK *)handle.AddrOfPinnedObject(); } else if (asyncResult != null && asyncResult.DataChunks != null) { _nativeResponse.Response_V1.EntityChunkCount = asyncResult.DataChunkCount; _nativeResponse.Response_V1.pEntityChunks = asyncResult.DataChunks; } else { _nativeResponse.Response_V1.EntityChunkCount = 0; _nativeResponse.Response_V1.pEntityChunks = null; } var cachePolicy = new HttpApi.HTTP_CACHE_POLICY(); var cacheTtl = CacheTtl; if (cacheTtl.HasValue && cacheTtl.Value > TimeSpan.Zero) { cachePolicy.Policy = HttpApi.HTTP_CACHE_POLICY_TYPE.HttpCachePolicyTimeToLive; cachePolicy.SecondsToLive = (uint)Math.Min(cacheTtl.Value.Ticks / TimeSpan.TicksPerSecond, Int32.MaxValue); } byte[] reasonPhraseBytes = HeaderEncoding.GetBytes(reasonPhrase); fixed(byte *pReasonPhrase = reasonPhraseBytes) { _nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length; _nativeResponse.Response_V1.pReason = (sbyte *)pReasonPhrase; fixed(HttpApi.HTTP_RESPONSE_V2 *pResponse = &_nativeResponse) { statusCode = HttpApi.HttpSendHttpResponse( RequestContext.RequestQueueHandle, Request.RequestId, (uint)flags, pResponse, &cachePolicy, &bytesSent, SafeLocalFree.Zero, 0, asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped, IntPtr.Zero); if (asyncResult != null && statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess) { asyncResult.BytesSent = bytesSent; // The caller will invoke IOCompleted } } } } finally { FreePinnedHeaders(pinnedHeaders); } return(statusCode); }