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
This commit is contained in:
Dan Polivy
2015-03-27 00:19:56 -07:00
committed by sgrebnov
parent dfd2c45142
commit 75d55ddaaa

View File

@@ -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<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>
@@ -209,6 +215,80 @@ namespace WPCordovaClassLib.Cordova.Commands
}
}
/// <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>
@@ -224,7 +304,7 @@ namespace WPCordovaClassLib.Cordova.Commands
/// </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 @@ 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<string, string> 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<string, string> headers = parseHeaders(downloadOptions.Headers);