using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Net; using System.Collections; namespace SalarSoft.ASProxy.BuiltIn.DataManagement { // CallMeLaNN: // Its better I extract the code one issue by one issue // since my code already quite messy because of just testing to make sure it work. // It work, but to be compatible with your environment, then we go through code by code. class Class1 { /// /// Reads cookies from response and saves them all in user pc /// /// proxy response instance public override void RestoreCookiesFromResponse(WebResponse webResponse, bool saveAsTemporary) { // --------------------------------------- // Declarations if (!(webResponse is HttpWebResponse) || HttpContext.Current == null) return; HttpResponse userResponse = HttpContext.Current.Response; HttpRequest userRequest = HttpContext.Current.Request; HttpWebResponse httpWebResponse = (HttpWebResponse)webResponse; Uri webUri = httpWebResponse.ResponseUri; // Moved by CallMeLaNN CookieContainer container = new CookieContainer(); ApplyRequestToCookieContainer(container, userRequest, webUri); CookieCollection responseCookies = httpWebResponse.Cookies; container.Add(webUri, responseCookies); BugFix_CookieContainer(container); CookieCollection coll = container.GetCookies(webUri); // if some cookies is sent if (httpWebResponse.Cookies.Count > 0) { // Get cookie from user request string cookieName = GetCookieName(webUri); HttpCookie reqCookie = userRequest.Cookies[cookieName]; // CookieContainer container = new CookieContainer(); // CallMeLaNN: removed. // Modified by CallMeLaNN // if there is cookies // restores cookies from user request // ApplyRequestToCookieContainer(container, userRequest, webUri, false); // CallMeLaNN: removed. // CookieCollection responseCookies = httpWebResponse.Cookies; // CallMeLaNN: removed. // add response cookies // this add overrides previous cookies // container.Add(webUri, responseCookies); // CallMeLaNN: removed. // BUGFIX: CookieContainer has a bug // Here is its bugfix // To get around this bug, the domains should start with a DOT // BugFix_AddDotCookieDomain(container); // CallMeLaNN: removed. // get cookie container cookies // BUG: CookieContainer has a bug // BUG: if the "domain" is ".site.com" or "site.com" it won't return any cookie for "http://site.com" // CallMeLaNN: I have tested the cookie domain ".google.com" and "google.com", CookieContainner.GetCookies() can return the cookie from uri "http://google.com" because of your BugFix_AddDotCookieDomain. // Tested in unit test only, not the actual request to server. // CookieCollection coll = container.GetCookies(webUri); // CallMeLaNN: removed. // get cookie header // CookieCollection coll = container.GetCookies(webUri); can't get all cookie header logically because it require Uri that can retrieve cookies on that domain and path only. // Cookies in deeper subdomain or deeper path will not taken. We never know what subdomains or paths available here. // So in order to get the all cookie in the entire domain, reflection is used. Or else we no need to use container earlier to do this. // Container is design for easy to us to get the suitable cookies in the current domain and path + expiracy management. // However Microsoft should add .GetAllCookies() method in case of this issue. CookieCollection allCookies = GetAllCookies(container); string cookieHeader = GetCookieHeader(allCookies); // if there is cookie header if (!string.IsNullOrEmpty(cookieHeader)) { reqCookie = new HttpCookie(cookieName); if (!saveAsTemporary) { // Since V5.0: To prevent cookie storage overflow, it should store cookies at most 7 days reqCookie.Expires = DateTime.Now.AddDays(7); } // value reqCookie.Value = HttpUtility.UrlEncode(cookieHeader); // CallMeLaNN: This is second layer encode // add to response userResponse.Cookies.Add(reqCookie); } } // AddRemoveJsCookie(userResponse.Cookies, coll, userRequest.Cookies, userRequest.Url.Scheme == Uri.UriSchemeHttps, webResponse.ResponseUri.Scheme == Uri.UriSchemeHttps, userRequest.ApplicationPath); } public CookieCollection GetAllCookies(CookieContainer cc) { CookieCollection lstCookies = new CookieCollection(); Hashtable table = (Hashtable)cc.GetType().InvokeMember("m_domainTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, cc, new object[] { }); foreach (object pathList in table.Values) { SortedList lstCookieCol = (SortedList)pathList.GetType().InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList, new object[] { }); foreach (CookieCollection colCookies in lstCookieCol.Values) foreach (Cookie c in colCookies) { lstCookies.Add(c); } } return lstCookies; } // Here actually I am not using cookie header format, but I use my own key=value pair. // You can change to make it compatible like cookie header like // cookiename=cookievalue instead of Name=cookiename; Value=cookievalue (I seperate name and value for faster coding and good parsing) // expires=... instead of Expires=... // but actually not necessary to be same like cookie header format because this will never use at http header, but just only stored in 'browser group cookie' and only used by this CookieManager. // This Fix will create Cookie by setting all required property value. // This will store Expires, HttpOnly etc that will managed by CookieContainer. // string -> cookie object // Note: I am not sure if this work with your modified RestoreCookiesFromResponse() public void ApplyRequestToCookieContainer(CookieContainer container, HttpRequest userRequest, Uri webUri) { if (container == null || userRequest == null || webUri == null) return; string cookieName = GetCookieName(webUri); HttpCookie reqCookie = userRequest.Cookies[cookieName]; if (reqCookie == null) return; //HttpCookie jsCookie; // If cookie value in encoded version, decode it first. This is second layer decode. (optional but required if encoded) string header = HttpUtility.UrlDecode(reqCookie.Value); if (string.IsNullOrEmpty(header.Trim())) return; // Use standard & as seperator in 'cookie value' instead of , because Expires can contain comma for GMT date time format and split by , will doing the wrong split. string[] cookies = header.Trim().Split('&'); string[] cookieProperties; string name, value; Cookie cookieObj; foreach (string cookie in cookies) { if (string.IsNullOrEmpty(cookie.Trim())) continue; cookieProperties = cookie.Trim().Split(';'); // cookie properties seperated by ; cookieObj = new Cookie(); foreach (string cookieProperty in cookieProperties) { string prop = cookieProperty.Trim(); if (string.IsNullOrEmpty(prop)) continue; // Can't use split by equal sign method since 'cookie value' can contain equal sign (like in google, PREF='ID=...') and break this parsing, // instead, find the first equal sign. // cookieKVP = prop.Split('='); int equIndex = prop.IndexOf('='); name = prop.Substring(0, equIndex).Trim(); value = prop.Substring(equIndex + 1, prop.Length - equIndex - 1).Trim(); // Note that this long property name (Name, Value, Expires, Domain, etc) can be do in short form (N, V, E, D, etc) to minimize cookie size. if (name == "Name") cookieObj.Name = value; else if (name == "Value") cookieObj.Value = HttpUtility.UrlDecode(value); // This is first layer decode. This is compulsory. else if (name == "Expires") cookieObj.Expires = DateTime.Parse(value); else if (name == "Domain") cookieObj.Domain = value; else if (name == "Path") cookieObj.Path = value; else if (name == "HttpOnly") cookieObj.HttpOnly = bool.Parse(value); else if (name == "Expired") cookieObj.Expired = bool.Parse(value); else if (name == "Secure") cookieObj.Secure = bool.Parse(value); else if (name == "Port") // noted that I am not sure about port number, not tested yet to filter it. cookieObj.Port = value; else if (name == "Version") cookieObj.Version = int.Parse(value); else if (name == "Discard") cookieObj.Discard = bool.Parse(value); else if (name == "Comment") cookieObj.Comment = value; else if (name == "CommentUri") cookieObj.CommentUri = new Uri(value); } container.Add(cookieObj); } BugFix_CookieContainer(container); // if there is cookies //for (int i = 0; i < userRequest.Cookies.Count; i++) //{ // HttpCookie cookie = userRequest.Cookies[i]; // if (IsASProxyCookie(cookie.Name)) // { // // BUGFIX: cookies header shouldn't be decoded // //string val = HttpUtility.UrlDecode(cookie.Value); // string val = cookie.Value; // // // CallMeLaNN: We should get host from current web request, not cookie name. // Uri host = GetASProxyCookieHost(cookie.Name); // if (host != null) // container.SetCookies(host, val); // } //} } // encode/serialize (group all cookies) by using key value pair (actually not exactly same like cookie header) // cookie object -> string private string GetCookieHeader(CookieCollection cookieCollection) { string result = ""; if (cookieCollection != null) { string cookieStr = null; for (int i = 0; i < cookieCollection.Count; i++) { Cookie cookie = cookieCollection[i]; // generate cookie // string cookieStr = CallCookieToServerString(cookie); // removed by CallMeLaNN // The If condition below only write/included if different than default value to minimize cookie size. // Note that this long property name can be do in short form and remove space to minimize cookie size. // Required values, I think: cookieStr = "Name=" + cookie.Name; // this actual 'cookie value' should be encoded to avoid ,&% and other special char used in the 'cookie value' that will break a seperator and parsing later. This is first layer encode. Compulsory. if (!string.IsNullOrEmpty(cookie.Value)) cookieStr += "; Value=" + HttpUtility.UrlEncode(cookie.Value); if (cookie.Expires != DateTime.MinValue) cookieStr += "; Expires=" + cookie.Expires.ToString(); if (!string.IsNullOrEmpty(cookie.Domain)) cookieStr += "; Domain=" + cookie.Domain; if (!string.IsNullOrEmpty(cookie.Path)) cookieStr += "; Path=" + cookie.Path; if (cookie.HttpOnly) cookieStr += "; HttpOnly=" + cookie.HttpOnly.ToString(); if (cookie.Expired) cookieStr += "; Expired=" + cookie.Expired.ToString(); if (cookie.Secure) cookieStr += "; Secure=" + cookie.Secure.ToString(); // Additional and rarely used or I don't know, just add if any values: if (!string.IsNullOrEmpty(cookie.Port)) cookieStr += "; Port=" + cookie.Port; if (cookie.Version != 0) cookieStr += "; Version=" + cookie.Version.ToString(); if (cookie.Discard) cookieStr += "; Discard=" + cookie.Discard.ToString(); if (!string.IsNullOrEmpty(cookie.Comment)) cookieStr += "; Comment=" + cookie.Comment.ToString(); if (cookie.CommentUri != null) cookieStr += "; CommentUri=" + cookie.CommentUri.AbsoluteUri; if (!string.IsNullOrEmpty(result)) // Use standard & as seperator in cookie value instead of , because Expires can contain comma for GMT date time format. result = result + "& " + cookieStr; else result = cookieStr; } } return result; } } }