using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.Text; using GitHub.Services.Common; namespace GitHub.Services.Client { internal static class CookieUtility { public static readonly String AcsMetadataRetrievalExceptionText = "Unable to retrieve ACS Metadata from '{0}'"; public static readonly String FedAuthCookieName = "FedAuth"; public static readonly String WindowsLiveSignOutUrl = "https://login.live.com/uilogout.srf"; public static readonly Uri WindowsLiveCookieDomain = new Uri("https://login.live.com/"); public static CookieCollection GetFederatedCookies(Uri cookieDomainAndPath) { CookieCollection result = null; Cookie cookie = GetCookieEx(cookieDomainAndPath, FedAuthCookieName).FirstOrDefault(); if (cookie != null) { result = new CookieCollection(); result.Add(cookie); for (Int32 x = 1; x < 50; x++) { String cookieName = FedAuthCookieName + x; cookie = GetCookieEx(cookieDomainAndPath, cookieName).FirstOrDefault(); if (cookie != null) { result.Add(cookie); } else { break; } } } return result; } public static CookieCollection GetFederatedCookies(String[] token) { CookieCollection result = null; if (token != null && token.Length > 0 && token[0] != null) { result = new CookieCollection(); result.Add(new Cookie(FedAuthCookieName, token[0])); for (Int32 x = 1; x < token.Length; x++) { String cookieName = FedAuthCookieName + x; if (token[x] != null) { Cookie cookie = new Cookie(cookieName, token[x]); cookie.HttpOnly = true; result.Add(cookie); } else { break; } } } return result; } public static CookieCollection GetFederatedCookies(IHttpResponse webResponse) { CookieCollection result = null; IEnumerable cookies = null; if (webResponse.Headers.TryGetValues("Set-Cookie", out cookies)) { foreach (String cookie in cookies) { if (cookie != null && cookie.StartsWith(CookieUtility.FedAuthCookieName, StringComparison.OrdinalIgnoreCase)) { // Only take the security token field of the cookie, and discard the rest String fedAuthToken = cookie.Split(';').FirstOrDefault(); Int32 index = fedAuthToken.IndexOf('='); if (index > 0 && index < fedAuthToken.Length - 1) { String name = fedAuthToken.Substring(0, index); String value = fedAuthToken.Substring(index + 1); result = result ?? new CookieCollection(); result.Add(new Cookie(name, value)); } } } } return result; } public static CookieCollection GetAllCookies(Uri cookieDomainAndPath) { CookieCollection result = null; List cookies = GetCookieEx(cookieDomainAndPath, null); foreach (Cookie cookie in cookies) { if (result == null) { result = new CookieCollection(); } result.Add(cookie); } return result; } public static void DeleteFederatedCookies(Uri cookieDomainAndPath) { CookieCollection cookies = GetFederatedCookies(cookieDomainAndPath); if (cookies != null) { foreach (Cookie cookie in cookies) { DeleteCookieEx(cookieDomainAndPath, cookie.Name); } } } public static void DeleteWindowsLiveCookies() { DeleteAllCookies(WindowsLiveCookieDomain); } public static void DeleteAllCookies(Uri cookieDomainAndPath) { CookieCollection cookies = GetAllCookies(cookieDomainAndPath); if (cookies != null) { foreach (Cookie cookie in cookies) { DeleteCookieEx(cookieDomainAndPath, cookie.Name); } } } public const UInt32 INTERNET_COOKIE_HTTPONLY = 0x00002000; [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool InternetGetCookieEx( String url, String cookieName, StringBuilder cookieData, ref Int32 size, UInt32 flags, IntPtr reserved); [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool InternetSetCookieEx( String url, String cookieName, String cookieData, UInt32 flags, IntPtr reserved); public static Boolean DeleteCookieEx(Uri cookiePath, String cookieName) { UInt32 flags = INTERNET_COOKIE_HTTPONLY; String path = cookiePath.ToString(); if (!path.EndsWith("/", StringComparison.Ordinal)) { path = path + "/"; } DateTime expiration = DateTime.UtcNow.AddYears(-1); String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}=0;expires={1};path=/;domain={2};httponly", cookieName, expiration.ToString("R"), cookiePath.Host); return InternetSetCookieEx(path, null, cookieData, flags, IntPtr.Zero); } public static Boolean SetCookiesEx( Uri cookiePath, CookieCollection cookies) { String path = cookiePath.ToString(); if (!path.EndsWith("/", StringComparison.Ordinal)) { path = path + "/"; } Boolean successful = true; foreach (Cookie cookie in cookies) { // This means it doesn't expire if (cookie.Expires.Year == 1) { continue; } String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}; path={1}; domain={2}; expires={3}; httponly", cookie.Value, cookie.Path, cookie.Domain, cookie.Expires.ToString("ddd, dd-MMM-yyyy HH:mm:ss 'GMT'")); successful &= InternetSetCookieEx(path, cookie.Name, cookieData, INTERNET_COOKIE_HTTPONLY, IntPtr.Zero); } return successful; } public static List GetCookieEx(Uri cookiePath, String cookieName) { UInt32 flags = INTERNET_COOKIE_HTTPONLY; List cookies = new List(); Int32 size = 256; StringBuilder cookieData = new StringBuilder(size); String path = cookiePath.ToString(); if (!path.EndsWith("/", StringComparison.Ordinal)) { path = path + "/"; } if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero)) { if (size < 0) { return cookies; } cookieData = new StringBuilder(size); if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero)) { return cookies; } } if (cookieData.Length > 0) { String[] cookieSections = cookieData.ToString().Split(new char[] { ';' }); foreach (String cookieSection in cookieSections) { String[] cookieParts = cookieSection.Split(new char[] { '=' }, 2); if (cookieParts.Length == 2) { Cookie cookie = new Cookie(); cookie.Name = cookieParts[0].TrimStart(); cookie.Value = cookieParts[1]; cookie.HttpOnly = true; cookies.Add(cookie); } } } return cookies; } } }