private unsafe List <GCHandle>?SerializeHeaders(bool isOpaqueUpgrade) { Headers.IsReadOnly = true; // Prohibit further modifications. HttpApiTypes.HTTP_UNKNOWN_HEADER[]? unknownHeaders = null; HttpApiTypes.HTTP_RESPONSE_INFO[]? knownHeaderInfo = null; List <GCHandle> pinnedHeaders; GCHandle gcHandle; 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 = HttpApiTypes.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)HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { numUnknownHeaders += headerPair.Value.Count; } else if (headerPair.Value.Count > 1) { numKnownMultiHeaders++; } // else known single-value header. } try { fixed(HttpApiTypes.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 = HttpApiTypes.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)HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { if (unknownHeaders == null) { unknownHeaders = new HttpApiTypes.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); _nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApiTypes.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 = (byte *)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 = (byte *)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 = (byte *)gcHandle.AddrOfPinnedObject(); } else { if (knownHeaderInfo == null) { knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[numKnownMultiHeaders]; gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); _nativeResponse.pResponseInfo = (HttpApiTypes.HTTP_RESPONSE_INFO *)gcHandle.AddrOfPinnedObject(); } knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders; knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length = (uint)Marshal.SizeOf <HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>(); HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS(); header.HeaderId = (HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum)lookup; header.Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only. HttpApiTypes.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApiTypes.HTTP_KNOWN_HEADER[headerValues.Count]; gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); header.KnownHeaders = (HttpApiTypes.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 = (byte *)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 = (HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS *)gcHandle.AddrOfPinnedObject(); _nativeResponse.ResponseInfoCount++; } } } } catch { FreePinnedHeaders(pinnedHeaders); throw; } return(pinnedHeaders); }
internal unsafe void SerializeTrailers(HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks, int currentChunk, List <GCHandle> pins) { Debug.Assert(currentChunk == dataChunks.Length - 1); Debug.Assert(HasTrailers); MakeTrailersReadOnly(); var trailerCount = 0; foreach (var trailerPair in Trailers) { trailerCount += trailerPair.Value.Count; } var pinnedHeaders = new List <GCHandle>(); var unknownHeaders = new HttpApiTypes.HTTP_UNKNOWN_HEADER[trailerCount]; var gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); dataChunks[currentChunk].DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkTrailers; dataChunks[currentChunk].trailers.trailerCount = (ushort)trailerCount; dataChunks[currentChunk].trailers.pTrailers = gcHandle.AddrOfPinnedObject(); try { var unknownHeadersOffset = 0; foreach (var headerPair in Trailers) { if (headerPair.Value.Count == 0) { continue; } var headerName = headerPair.Key; var headerValues = headerPair.Value; for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++) { // Add Name var bytes = HeaderEncoding.GetBytes(headerName); unknownHeaders[unknownHeadersOffset].NameLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[unknownHeadersOffset].pName = (byte *)gcHandle.AddrOfPinnedObject(); // Add Value var headerValue = headerValues[headerValueIndex] ?? string.Empty; bytes = HeaderEncoding.GetBytes(headerValue); unknownHeaders[unknownHeadersOffset].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[unknownHeadersOffset].pRawValue = (byte *)gcHandle.AddrOfPinnedObject(); unknownHeadersOffset++; } } Debug.Assert(unknownHeadersOffset == trailerCount); } catch { FreePinnedHeaders(pinnedHeaders); throw; } // Success, keep the pins. pins.AddRange(pinnedHeaders); }
// From HttpSys, except does not write to response private unsafe List <GCHandle> SerializeHeaders(HttpApiTypes.HTTP_RESPONSE_V2 *pHttpResponse) { HttpResponseHeaders.IsReadOnly = true; HttpApiTypes.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; HttpApiTypes.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; var pinnedHeaders = new List <GCHandle>(); GCHandle gcHandle; if (HttpResponseHeaders.Count == 0) { return(null); } string headerName; string headerValue; int lookup; var numUnknownHeaders = 0; int numKnownMultiHeaders = 0; byte[] bytes = null; foreach (var headerPair in HttpResponseHeaders) { if (headerPair.Value.Count == 0) { continue; } lookup = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key); if (lookup == -1) // TODO handle opaque stream upgrade? { numUnknownHeaders++; } else if (headerPair.Value.Count > 1) { numKnownMultiHeaders++; } } try { var pKnownHeaders = &pHttpResponse->Response_V1.Headers.KnownHeaders; foreach (var headerPair in HttpResponseHeaders) { if (headerPair.Value.Count == 0) { continue; } headerName = headerPair.Key; StringValues headerValues = headerPair.Value; lookup = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); if (lookup == -1) { if (unknownHeaders == null) { unknownHeaders = new HttpApiTypes.HTTP_UNKNOWN_HEADER[numUnknownHeaders]; gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pHttpResponse->Response_V1.Headers.pUnknownHeaders = (HttpApiTypes.HTTP_UNKNOWN_HEADER *)gcHandle.AddrOfPinnedObject(); pHttpResponse->Response_V1.Headers.UnknownHeaderCount = 0; // to remove the iis header for server=... } for (var headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++) { // Add Name bytes = HeaderEncoding.GetBytes(headerName); unknownHeaders[pHttpResponse->Response_V1.Headers.UnknownHeaderCount].NameLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[pHttpResponse->Response_V1.Headers.UnknownHeaderCount].pName = (byte *)gcHandle.AddrOfPinnedObject(); // Add Value headerValue = headerValues[headerValueIndex] ?? string.Empty; bytes = HeaderEncoding.GetBytes(headerValue); unknownHeaders[pHttpResponse->Response_V1.Headers.UnknownHeaderCount].RawValueLength = (ushort)bytes.Length; gcHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); unknownHeaders[pHttpResponse->Response_V1.Headers.UnknownHeaderCount].pRawValue = (byte *)gcHandle.AddrOfPinnedObject(); pHttpResponse->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 = (byte *)gcHandle.AddrOfPinnedObject(); } else { if (knownHeaderInfo == null) { knownHeaderInfo = new HttpApiTypes.HTTP_RESPONSE_INFO[numKnownMultiHeaders]; gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); pHttpResponse->pResponseInfo = (HttpApiTypes.HTTP_RESPONSE_INFO *)gcHandle.AddrOfPinnedObject(); } knownHeaderInfo[pHttpResponse->ResponseInfoCount].Type = HttpApiTypes.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders; knownHeaderInfo[pHttpResponse->ResponseInfoCount].Length = (uint)Marshal.SizeOf <HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS>(); var header = new HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS(); header.HeaderId = (HttpApiTypes.HTTP_RESPONSE_HEADER_ID.Enum)lookup; header.Flags = HttpApiTypes.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only. var nativeHeaderValues = new HttpApiTypes.HTTP_KNOWN_HEADER[headerValues.Count]; gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned); pinnedHeaders.Add(gcHandle); header.KnownHeaders = (HttpApiTypes.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 = (byte *)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[pHttpResponse->ResponseInfoCount].pInfo = (HttpApiTypes.HTTP_MULTIPLE_KNOWN_HEADERS *)gcHandle.AddrOfPinnedObject(); pHttpResponse->ResponseInfoCount++; } } } catch (Exception) { FreePinnedHeaders(pinnedHeaders); throw; } return(pinnedHeaders); }