ANSI/UNICODE bug in System.Net.HttpListenerRequest

While testing what was the best function to hook in HttpListenerRequest (see Hooking HttpApi.dll's HttpReceiveHttpRequest) I found what I think is a bug in the 2.0 method System.Net.HttpListenerRequest which belongs to the Platform SDK: HTTP API]:

'' if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0)) {     this.m_CookedUrl = Marshal.PtrToStringAnsi((IntPtr) memoryBlob.RequestBlob.CookedUrl.pFullUrl, memoryBlob.RequestBlob.CookedUrl.FullUrlLength); }''
 * open System.Net.HttpListenerRequest in reflector
 * in the internal unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob) method look for this:
 * this.m_CookedUrl is an private string m_CookedUrl;
 * and memoryBlob.RequestBlob.CookedUrl.pFullUrl is an internal unsafe ushort* pFullUrl; (i.e. a pointer).
 * If we look at the unmanaged definitions ( see Hooking HttpApi.dll's HttpReceiveHttpRequest) we have:
 * HttpReceiveHttpRequest ->
 * _HTTP_REQUEST pRequestBuffer ->
 * HTTP_COOKED_URL CookedUrl ->
 * PCWSTR pFullUrl


 * The problem is that unManaged version of pFullUrl is in Unicode format, where the BCL version handles it as it was ASCII


 * I noticed this problem because System.Net.HttpListenerRequest.m_cookedUrl, had a value of "h\0t\0t\0p\0:\0/\0/\0l\0o\0c\0a\0l\0h\0o\0s\0t\0:\08\00\09\00\0/" which if you remove the \0 is "http://localhost:8090/"


 * Looking at the definition of Marshal.PtrToStringAnsi in MSDN we see that there are two definitions of Marshal.PtrToStringAnsi Method (HttpReceiveHttpRequest uses the second one)
 * public static string PtrToStringAnsi(IntPtr); Copies all characters up to the first null from an unmanaged ANSI string to a managed String. Widens each ANSI character to Unicode.
 * public static string PtrToStringAnsi(IntPtr, int); Allocates a managed String, copies a specified number of characters from an unmanaged ANSI string into it, and widens each ANSI character to Unicode.


 * The source code of public static string PtrToStringAnsi(IntPtr); is (using reflector):

[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] public static string PtrToStringAnsi(IntPtr ptr) {  if (Win32Native.NULL == ptr) {     return null; }  if (Marshal.IsWin32Atom(ptr)) {     return null; }  int num1 = Win32Native.lstrlenA(ptr); if (num1 == 0) {     return string.Empty; }  StringBuilder builder1 = new StringBuilder(num1); Win32Native.CopyMemoryAnsi(builder1, ptr, new IntPtr(1 + num1)); return builder1.ToString; }


 * The source code of public static string PtrToStringAnsi(IntPtr, int); (using reflector) is:

[MethodImpl(MethodImplOptions.InternalCall), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] public static extern string PtrToStringAnsi(IntPtr ptr, int len);


 * we see that it is an internalCall method ("MethodImplOptions.InternalCall" is used to declare a method in managed code that has no managed implementation. The implementation (unmanaged) is supplied by the runtime itself (CLR)):


 * Since we don't have the source code of the CLR and the 2.0 version of Rotor, I had to look at Rotor's version 1.1 where I found (using CodeScoping's cool feature for searching text in files:) ) inside (\sscli\clr\src\vm\comndirect.cpp) this:

 * PInvoke.PtrToStringAnsi */ FCIMPL2(Object*, PtrToStringAnsi, LPVOID ptr, INT32 len) {               STRINGREF pString = NULL; HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, pString); //-[autocvtpro]--- THROWSCOMPLUSEXCEPTION; if (ptr == NULL) COMPlusThrowArgumentNull(L"ptr"); if (len < 0) COMPlusThrowNonLocalized(kArgumentException, L"len"); int nwc = 0; if (len != 0) {

nwc = MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)(ptr),len,NULL,0); if (nwc == 0)COMPlusThrow(kArgumentException, IDS_UNI2ANSI_FAILURE);

}               pString = COMString::NewString(nwc); MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)(ptr),len,pString->GetBuffer,nwc); //-[autocvtepi]--- HELPER_METHOD_FRAME_END; return OBJECTREFToObject(pString); } FCIMPLEND 
 * In summary:
 * The problem is in here:

internal unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob) {   ....        if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0)) {           this.m_CookedUrl = Marshal.PtrToStringAnsi((IntPtr) memoryBlob.RequestBlob.CookedUrl.pFullUrl, memoryBlob.RequestBlob.CookedUrl.FullUrlLength) }   .... }
 * since it should be Marshal.PtrToStringUni and not Marshal.PtrToStringAnsi

Note1: as far as I can tell m_CookedUrl is only used here:

private System.Net.HttpListenerRequest.Uri get_RequestUri

{ ....       if (!string.IsNullOrEmpty(this.m_CookedUrl)) {           flag1 = Uri.TryCreate(this.m_CookedUrl, UriKind.Absolute, out this.m_RequestUri); }       .... }

Which probably explains why this was not picked up by the MS .Net team (i.e. No aparent side effects)

Note2: using the detoured HttpReceiveHttpRequest I changed the contents of (*pRequestBuffer).CookedUrl.pFullUrl to

StringCbPrintfA((STRSAFE_LPSTR)(*pRequestBuffer).CookedUrl.pFullUrl,30,"http://localhost:8090/Test.aspx");

and I got “http://localhost:8090/Test.aspx*&(&(^(^(*&(*&(*&” (i.e. The original ANSI string + strlen(original ANSI string) chars) in System.Net.HttpListenerRequest.m_CookedUrl (which shows that the original assumption was that (*pRequestBuffer).CookedUrl.pFullUrl should have an ANSI string.