Search code examples
c#pinvokehttpcookie

PInvoike InternetGetCookieEx2 returns Error 12006


I am trying to retrieve cookies using c#, but I get an error 12006 complaining about the URL. I use InternetGetCookieEx successfully to retrieve cookie data, but I need to retrieve the entire cookie for my current exercise

I'm not sure If I call it incorrectly, or my flags is not correct, but there are only 4 flags, and 2 of them is retrictive. My problem I believe is the pointer of the array of INTERNET_COOKIE2 that needs to returned. I am really stuck here and need some guidance. This is my code

  [DllImport("wininet.dll", SetLastError = true)]
  internal static extern bool InternetGetCookieEx2(
          string url,
          string cookieName,
          Int32 dwFlags,
          INTERNET_COOKIE[] cookie,
          ref Int32 cookieCount);

  [SecurityCritical]
  public static Cookie[] GetCookieInternal(Uri uri, bool throwIfNoCookie)
  {
     int cookieCount = 0;
     string url = UriToString(uri);
     int flag = 0;

     DemandWebPermission(uri);
     //There is no error here, but cookieCount comes back as 0. If I ommit this code and
     //pass in the array directly I get the 12006 error. url is valid. I've checked multiple times
     if (CookieHelper.InternetGetCookieEx2(url, null, flag, null, ref cookieCount))  
     {
        if (cookieCount > 0)
        {
           CookieHelper.INTERNET_COOKIE[] pchCookies = new CookieHelper.INTERNET_COOKIE[cookieCount];
           if (CookieHelper.InternetGetCookieEx2(url, null, flag, pchCookies, ref cookieCount))
           {
              var result = new Cookie[cookieCount];
              for (int i = 0; i < cookieCount; i++)
              {
                 result[i] = new Cookie
                 {
                    HttpOnly = (pchCookies[i].dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_HTTPONLY) > 0,
                    Domain = pchCookies[i].pwszDomain,
                    Expired = pchCookies[i].fExpiresSet,
                    Expires = pchCookies[i].fExpiresSet ? DateTime.FromFileTime(pchCookies[i].ftExpires.dwHighDateTime) : DateTime.MinValue,
                    Name = pchCookies[i].pwszName,
                    Path = pchCookies[i].pwszPath,
                    Secure = (pchCookies[i].dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_IS_SECURE) > 0,
                    Value = pchCookies[i].pwszValue
                 };
              }
              return result;
           }
        }
     }

     int lastErrorCode = Marshal.GetLastWin32Error();

     if (throwIfNoCookie || (lastErrorCode != (int)CookieHelper.ErrorFlags.ERROR_NO_MORE_ITEMS))
     {
        throw new Win32Exception(lastErrorCode);
     }

     return null;
  }

  private static void DemandWebPermission(Uri uri)
  {
     string uriString = UriToString(uri);

     if (uri.IsFile)
     {
        string localPath = uri.LocalPath;
        new FileIOPermission(FileIOPermissionAccess.Read, localPath).Demand();
     }
     else
     {
        new WebPermission(NetworkAccess.Connect, uriString).Demand();
     }
  }

  private static string UriToString(Uri uri)
  {
     if (uri == null)
     {
        throw new ArgumentNullException("uri");
     }

     UriComponents components = (uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString);
     return new StringBuilder(uri.GetComponents(components, UriFormat.SafeUnescaped), 2083).ToString();
  }

UPDATE Working Code Here is my code for anyone in the future.

public class CookieHelper
{
  [DllImport("wininet.dll", CharSet = CharSet.Unicode)]
  internal static extern int InternetGetCookieEx2(
          string url,
          string cookieName,
          int dwFlags,
          [Out] out IntPtr cookie,
          [Out] out int cookieCount);


  [DllImport("wininet.dll")]
  internal static extern void InternetFreeCookies(
            IntPtr pCookies,
            int dwCookieCount);

  public enum InternetFlags
  {
     INTERNET_COOKIE_IS_SECURE = 32,
     INTERNET_COOKIE_HTTPONLY = 8192, //Requires IE 8 or higher   
     INTERNET_COOKIE_THIRD_PARTY = 131072,
     INTERNET_FLAG_RESTRICTED_ZONE = 16
  }

  public struct INTERNET_COOKIE
  {
     public IntPtr pwszName;
     public IntPtr pwszValue;
     public IntPtr pwszDomain;
     public IntPtr pwszPath;
     public int dwFlags;
     public System.Runtime.InteropServices.ComTypes.FILETIME ftExpires;
     public bool fExpiresSet;
  }
}

public class FullWebBrowserCookie
{
  /// <summary>
  /// Internal Get Cookie
  /// </summary>
  /// <param name="uri"></param>
  /// <param name="throwIfNoCookie"></param>
  /// <returns></returns>
  [SecurityCritical]
  public static Cookie[] GetCookieInternal(Uri uri, bool throwIfNoCookie)
  {
     string url = UriToString(uri);
     int flag = 0;  //Change to 4096 if you want to retrieve HTTP Only cookies

     DemandWebPermission(uri);
     var error = CookieHelper.InternetGetCookieEx2(url, null, flag, out var ptr, out var cookieCount);
     if (error != 0)
        throw new Win32Exception(error);
     if (ptr == IntPtr.Zero)
     {
        if (throwIfNoCookie)
           throw new InvalidOperationException("No cookie");

        return Array.Empty<Cookie>();
     }
     try
     {
        if (throwIfNoCookie && cookieCount == 0)
           throw new InvalidOperationException("No cookie");
        var result = new Cookie[cookieCount];
        var size = Marshal.SizeOf<CookieHelper.INTERNET_COOKIE>();

        for (int i = 0; i < cookieCount; i++)
        {
           var pchCookie = Marshal.PtrToStructure<CookieHelper.INTERNET_COOKIE>(ptr + i * size);
            result[i] = new Cookie
            {
               HttpOnly = (pchCookie.dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_HTTPONLY) > 0,
               Domain = Marshal.PtrToStringAuto(pchCookie.pwszDomain),
               Expires = pchCookie.fExpiresSet ? DateTime.FromFileTime((((long)pchCookie.ftExpires.dwHighDateTime) << 32) + pchCookie.ftExpires.dwLowDateTime) : DateTime.MinValue,
               Expired = pchCookie.fExpiresSet && DateTime.FromFileTime((((long)pchCookie.ftExpires.dwHighDateTime) << 32) + pchCookie.ftExpires.dwLowDateTime) < DateTime.Now,
               Name = Marshal.PtrToStringAuto(pchCookie.pwszName),
               Path = Marshal.PtrToStringAuto(pchCookie.pwszPath),
               Secure = (pchCookie.dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_IS_SECURE) > 0,
               Value = Marshal.PtrToStringAuto(pchCookie.pwszValue)
            };
        }
        return result;
     }
     finally
     {
        CookieHelper.InternetFreeCookies(ptr, cookieCount);
     }

  }

  private static void DemandWebPermission(Uri uri)
  {
     string uriString = UriToString(uri);

     if (uri.IsFile)
     {
        string localPath = uri.LocalPath;
        new FileIOPermission(FileIOPermissionAccess.Read, localPath).Demand();
     }
     else
     {
        new WebPermission(NetworkAccess.Connect, uriString).Demand();
     }
  }

  private static string UriToString(Uri uri)
  {
     if (uri == null)
     {
        throw new ArgumentNullException("uri");
     }

     UriComponents components = (uri.IsAbsoluteUri ? UriComponents.AbsoluteUri : UriComponents.SerializationInfoString);
     return new StringBuilder(uri.GetComponents(components, UriFormat.SafeUnescaped), 2083).ToString();
  }

}

Solution

  • You seems to have mixed up the docs for InternetGetCookieEx2 with the docs for InternetGetCookieEx.

    You have a number of issues:

    • The return code is the error itself. InternetGetCookieEx2 does not set the last error code, it just returns it.
    • Need to specify CharSet.Unicode.
    • The pointer to the buffer is not one that you supply. It's a pointer to a pointer to the buffer, and you need to free that buffer. InternetGetCookieEx2 is not documented to return partial results, you will get everything in a single call.
    • Therefore you also cannot use automatic marshalling. You need to use PtrToStructure manually.
    • You also need to add Unicode to your struct, so that the strings are marshalled correctly.
    [DllImport("wininet.dll", CharSet = CharSet.Unicode)]
    internal static extern int InternetGetCookieEx2(
              string? url,
              string? cookieName,
              int dwFlags,
              [Out] out IntPtr cookie,
              [Out] out int cookieCount);
    
    [DllImport("wininet.dll")]
    internal static extern void InternetFreeCookies(
              IntPtr pCookies,
              int dwCookieCount);
    
    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
    public struct INTERNET_COOKIE2
    {
        public string pwszName;
        public string pwszValue;
        public string pwszDomain;
        public string pwszPath;
        public int dwFlags;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftExpires;
        public bool fExpiresSet;
    }
    
    [SecurityCritical]
    public static Cookie[] GetCookieInternal(Uri uri, bool throwIfNoCookie)
    {
        string url = UriToString(uri);
        int flag = 0;
    
        DemandWebPermission(uri);
        var error = CookieHelper.InternetGetCookieEx2(url, null, flag, out var ptr, out var cookieCount);
        if (error != 0)
            throw new Win32Exception(error);
        if (ptr == IntPtr.Zero)
        {
            if (throwIfNoCookie)
                throw new InvalidOperationException("No cookie");
    
            return Array.Empty<Cookie>();
        }
    
        try
        {
            if (throwIfNoCookie && cookieCount == 0)
                throw new InvalidOperationException("No cookie");
    
            var result = new Cookie[cookieCount];
            var size = Marshal.SizeOf<CookieHelper.INTERNET_COOKIE>();
            for (int i = 0; i < cookieCount; i++)
            {
                var pchCookie = Marshal.PtrToStructure<CookieHelper.INTERNET_COOKIE>(ptr + i * size)
                result[i] = new Cookie
                {
                    HttpOnly = (pchCookie.dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_HTTPONLY) > 0,
                    Domain = pchCookie.pwszDomain,
                    Expired = pchCookie.fExpiresSet,
                    Expires = pchCookie.fExpiresSet ? DateTime.FromFileTime(pchCookie.ftExpires.dwHighDateTime) : DateTime.MinValue,
                    Name = pchCookie.pwszName,
                    Path = pchCookie.pwszPath,
                    Secure = (pchCookie.dwFlags & (int)CookieHelper.InternetFlags.INTERNET_COOKIE_IS_SECURE) > 0,
                    Value = pchCookie.pwszValue
                };
            }
            return result;
        }
        finally
        {
            InternetFreeCookies(ptr, cookieCount);        
        }
    }