From 75d55ddaaa184f8d48b3ecaf2071fe858596359c Mon Sep 17 00:00:00 2001 From: Dan Polivy Date: Fri, 27 Mar 2015 00:19:56 -0700 Subject: [PATCH] CB-8761 [wp8]: Copy cookies from WebBrowser On other platforms, the cookies from the browser that apply to the request are automatically included. On WP8, that's not the case. This commit will copy the cookies from the WebBrowser control, if the scheme and host of the WebBrowser match that of the HttpWebRequest. For users who host their web components remotely, and perform uploads/downloads from the same server using cookie based authentication, this fix enables that scenario on WP8, and brings it up to parity with iOS, Android, and others. Fixes https://issues.apache.org/jira/browse/CB-8761 CB-8761 [wp8]: Cleanup per comments github close #74 --- src/wp/FileTransfer.cs | 92 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/wp/FileTransfer.cs b/src/wp/FileTransfer.cs index 1ddfc28..e1f9fb0 100644 --- a/src/wp/FileTransfer.cs +++ b/src/wp/FileTransfer.cs @@ -12,6 +12,7 @@ limitations under the License. */ +using Microsoft.Phone.Controls; using System; using System.Collections.Generic; using System.IO; @@ -21,6 +22,7 @@ using System.Runtime.Serialization; using System.Windows; using System.Security; using System.Diagnostics; +using System.Threading.Tasks; namespace WPCordovaClassLib.Cordova.Commands { @@ -86,6 +88,10 @@ namespace WPCordovaClassLib.Cordova.Commands private static Dictionary InProcDownloads = new Dictionary(); + // Private instance of the main WebBrowser instance + // NOTE: Any access to this object needs to occur on the UI thread via the Dispatcher + private WebBrowser browser; + /// /// Uploading response info /// @@ -209,6 +215,80 @@ namespace WPCordovaClassLib.Cordova.Commands } } + /// + /// Helper method to copy all relevant cookies from the WebBrowser control into a header on + /// the HttpWebRequest + /// + /// The source browser to copy the cookies from + /// The destination HttpWebRequest to add the cookie header to + /// Nothing + private async Task CopyCookiesFromWebBrowser(HttpWebRequest webRequest) + { + var tcs = new TaskCompletionSource(); + + // Accessing WebBrowser needs to happen on the UI thread + Deployment.Current.Dispatcher.BeginInvoke(() => + { + // Get the WebBrowser control + if (this.browser == null) + { + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null) + { + CordovaView cView = page.FindName("CordovaView") as CordovaView; + if (cView != null) + { + this.browser = cView.Browser; + } + } + } + } + + try + { + // Only copy the cookies if the scheme and host match (to avoid any issues with secure/insecure cookies) + // NOTE: since the returned CookieCollection appears to munge the original cookie's domain value in favor of the actual Source domain, + // we can't know for sure whether the cookies would be applicable to any other hosts, so best to play it safe and skip for now. + if (this.browser != null && this.browser.Source.IsAbsoluteUri == true && + this.browser.Source.Scheme == webRequest.RequestUri.Scheme && this.browser.Source.Host == webRequest.RequestUri.Host) + { + string cookieHeader = ""; + string requestPath = webRequest.RequestUri.PathAndQuery; + CookieCollection cookies = this.browser.GetCookies(); + + // Iterate over the cookies and add to the header + foreach (Cookie cookie in cookies) + { + // Check that the path is allowed, first + // NOTE: Path always seems to be empty for now, even if the cookie has a path set by the server. + if (cookie.Path.Length == 0 || requestPath.IndexOf(cookie.Path, StringComparison.InvariantCultureIgnoreCase) == 0) + { + cookieHeader += cookie.Name + "=" + cookie.Value + "; "; + } + } + + // Finally, set the header if we found any cookies + if (cookieHeader.Length > 0) + { + webRequest.Headers["Cookie"] = cookieHeader; + } + } + } + catch (Exception) + { + // Swallow the exception + } + + // Complete the task + tcs.SetResult(Type.Missing); + }); + + await tcs.Task; + } + /// /// Upload options /// @@ -224,7 +304,7 @@ namespace WPCordovaClassLib.Cordova.Commands /// /// Upload options /// exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); - public void upload(string options) + public async void upload(string options) { options = options.Replace("{}", ""); // empty objects screw up the Deserializer string callbackId = ""; @@ -283,6 +363,10 @@ namespace WPCordovaClassLib.Cordova.Commands webRequest.ContentType = "multipart/form-data; boundary=" + Boundary; webRequest.Method = uploadOptions.Method; + // Associate cookies with the request + // This is an async call, so we need to await it in order to preserve proper control flow + await CopyCookiesFromWebBrowser(webRequest); + if (!string.IsNullOrEmpty(uploadOptions.Headers)) { Dictionary headers = parseHeaders(uploadOptions.Headers); @@ -341,7 +425,7 @@ namespace WPCordovaClassLib.Cordova.Commands return null; } - public void download(string options) + public async void download(string options) { TransferOptions downloadOptions = null; HttpWebRequest webRequest = null; @@ -475,6 +559,10 @@ namespace WPCordovaClassLib.Cordova.Commands state.request = webRequest; InProcDownloads[downloadOptions.Id] = state; + // Associate cookies with the request + // This is an async call, so we need to await it in order to preserve proper control flow + await CopyCookiesFromWebBrowser(webRequest); + if (!string.IsNullOrEmpty(downloadOptions.Headers)) { Dictionary headers = parseHeaders(downloadOptions.Headers);