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);
}