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
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.Windows;
 using System.Security;
 using System.Diagnostics;
+using System.Threading.Tasks;
 
 namespace WPCordovaClassLib.Cordova.Commands
 {
@@ -86,6 +88,10 @@
 
         private static Dictionary<string, DownloadRequestState> InProcDownloads = new Dictionary<string,DownloadRequestState>();
 
+        // 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;
+
         /// <summary>
         /// Uploading response info
         /// </summary>
@@ -210,6 +216,80 @@
         }
 
         /// <summary>
+        /// Helper method to copy all relevant cookies from the WebBrowser control into a header on
+        /// the HttpWebRequest
+        /// </summary>
+        /// <param name="browser">The source browser to copy the cookies from</param>
+        /// <param name="webRequest">The destination HttpWebRequest to add the cookie header to</param>
+        /// <returns>Nothing</returns>
+        private async Task CopyCookiesFromWebBrowser(HttpWebRequest webRequest)
+        {
+            var tcs = new TaskCompletionSource<object>();
+
+            // 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;
+        }
+
+        /// <summary>
         /// Upload options
         /// </summary>
         //private TransferOptions uploadOptions;
@@ -224,7 +304,7 @@
         /// </summary>
         /// <param name="options">Upload options</param>
         /// 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 @@
                 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<string, string> headers = parseHeaders(uploadOptions.Headers);
@@ -341,7 +425,7 @@
             return null;
         }
 
-        public void download(string options)
+        public async void download(string options)
         {
             TransferOptions downloadOptions = null;
             HttpWebRequest webRequest = null;
@@ -475,6 +559,10 @@
                 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<string, string> headers = parseHeaders(downloadOptions.Headers);