Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New informations request #64

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions HybridWebView/HybridWebViewProxyEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,61 @@ public class HybridWebViewProxyEventArgs
/// Creates a new instance of <see cref="HybridWebViewProxyEventArgs"/>.
/// </summary>
/// <param name="fullUrl">The full request URL.</param>
public HybridWebViewProxyEventArgs(string fullUrl)
public HybridWebViewProxyEventArgs(string fullUrl, string? method, IDictionary<string, string>? headers)
{
Url = fullUrl;
QueryParams = QueryStringHelper.GetKeyValuePairs(fullUrl);
RequestHeaders = headers ?? new Dictionary<string, string>();

if (Enum.TryParse(method ?? "GET", true, out HttpMethod methodResult))
{
Method = methodResult;
}
}

/// <summary>
/// The full request URL.
/// </summary>
public string Url { get; }

/// <summary>
/// The request method.
/// </summary>
public HttpMethod Method { get; }

/// <summary>
/// Query string values extracted from the request URL.
/// </summary>
public IDictionary<string, string> QueryParams { get; }

/// <summary>
/// The response content type.
/// Header strings values extracted from the request.
/// </summary>
public IDictionary<string, string> RequestHeaders { get; }

/// <summary>
/// The response headers to be used to respond to the request.
/// </summary>
public IDictionary<string, string>? ResponseHeaders { get; set; }

/// <summary>
/// The response content type.
/// </summary>
public string? ResponseContentType { get; set; } = "text/plain";

/// <summary>
/// The response stream to be used to respond to the request.
/// </summary>
public Stream? ResponseStream { get; set; } = null;

}

public enum HttpMethod
{
GET,
POST,
PUT,
DELETE,
OPTIONS
}
}
24 changes: 18 additions & 6 deletions HybridWebView/Platforms/Android/AndroidHybridWebViewClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public AndroidHybridWebViewClient(HybridWebViewHandler handler) : base(handler)
{
var fullUrl = request?.Url?.ToString();
var requestUri = QueryStringHelper.RemovePossibleQueryString(fullUrl);
var method = request?.Method;
var headers = request?.RequestHeaders;

var webView = (HybridWebView)_handler.VirtualView;

Expand All @@ -44,11 +46,12 @@ public AndroidHybridWebViewClient(HybridWebViewHandler handler) : base(handler)
}

Stream? contentStream = null;
IDictionary<string, string> responseHeaders = null;

// Check to see if the request is a proxy request.
if (relativePath == HybridWebView.ProxyRequestPath)
{
var args = new HybridWebViewProxyEventArgs(fullUrl);
var args = new HybridWebViewProxyEventArgs(fullUrl, method, headers);

// TODO: Don't block async. Consider making this an async call, and then calling DidFinish when done
webView.OnProxyRequestMessage(args).Wait();
Expand All @@ -57,6 +60,7 @@ public AndroidHybridWebViewClient(HybridWebViewHandler handler) : base(handler)
{
contentType = args.ResponseContentType ?? "text/plain";
contentStream = args.ResponseStream;
responseHeaders = args.ResponseHeaders;
}
}

Expand All @@ -83,7 +87,7 @@ public AndroidHybridWebViewClient(HybridWebViewHandler handler) : base(handler)
else
{
// TODO: We don't know the content length because Android doesn't tell us. Seems to work without it!
return new WebResourceResponse(contentType, "UTF-8", 200, "OK", GetHeaders(contentType), contentStream);
return new WebResourceResponse(contentType, "UTF-8", 200, "OK", GetHeaders(contentType, responseHeaders), contentStream);
}
}
else
Expand All @@ -106,9 +110,17 @@ public AndroidHybridWebViewClient(HybridWebViewHandler handler) : base(handler)
}
}

private protected static IDictionary<string, string> GetHeaders(string contentType) =>
new Dictionary<string, string> {
{ "Content-Type", contentType },
};
private protected static IDictionary<string, string> GetHeaders(string contentType, IDictionary<string, string>? baseHeaders = null)
{
if (baseHeaders == null) baseHeaders = new Dictionary<string, string>();

if (baseHeaders.ContainsKey("Content-Type") == false)
{
baseHeaders["Content-Type"] = contentType;
}

return baseHeaders;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,25 @@ public SchemeHandler(HybridWebViewHandler webViewHandler)
[SupportedOSPlatform("ios11.0")]
public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
{
var url = urlSchemeTask.Request.Url?.AbsoluteString ?? "";

var responseData = await GetResponseBytes(url);
var responseData = await GetResponseBytes(urlSchemeTask);

if (responseData.StatusCode == 200)
{
using (var dic = new NSMutableDictionary<NSString, NSString>())
var keys = responseData.headers?.Keys?.Select(p => new NSString(p)) ?? Array.Empty<NSString>();
var values = responseData.headers?.Values?.Select(p => new NSString(p)) ?? Array.Empty<NSString>();

using (var dic = new NSMutableDictionary<NSString, NSString>(keys.ToArray(), values.ToArray()))
{
dic.Add((NSString)"Content-Length", (NSString)(responseData.ResponseBytes.Length.ToString(CultureInfo.InvariantCulture)));
dic.Add((NSString)"Content-Type", (NSString)responseData.ContentType);
if (dic.ContainsKey((NSString)"Content-Length") == false)
{
dic.Add((NSString)"Content-Length", (NSString)(responseData.ResponseBytes.Length.ToString(CultureInfo.InvariantCulture)));
}

if (dic.ContainsKey((NSString)"Content-Type") == false)
{
dic.Add((NSString)"Content-Type", (NSString)responseData.ContentType);
}

// Disable local caching. This will prevent user scripts from executing correctly.
dic.Add((NSString)"Cache-Control", (NSString)"no-cache, max-age=0, must-revalidate, no-store");
if (urlSchemeTask.Request.Url != null)
Expand All @@ -92,13 +101,17 @@ public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSche
}
}

private async Task<(byte[] ResponseBytes, string ContentType, int StatusCode)> GetResponseBytes(string? url)
private async Task<(byte[] ResponseBytes, string ContentType, int StatusCode, IDictionary<string, string>? headers)> GetResponseBytes(IWKUrlSchemeTask urlSchemeTask)
{
var url = urlSchemeTask.Request.Url?.AbsoluteString ?? "";
var method = urlSchemeTask.Request.HttpMethod;
var requestHeaders = urlSchemeTask.Request.Headers?.ToDictionary(p => p.Key.ToString(), p => p.Value.ToString());

string contentType;

string fullUrl = url;
url = QueryStringHelper.RemovePossibleQueryString(url);

if (new Uri(url) is Uri uri && HybridWebView.AppOriginUri.IsBaseOf(uri))
{
var relativePath = HybridWebView.AppOriginUri.MakeRelativeUri(uri).ToString().Replace('\\', '/');
Expand All @@ -125,18 +138,20 @@ public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSche
}

Stream? contentStream = null;
IDictionary<string, string>? responseHeaders = null;

// Check to see if the request is a proxy request.
if (relativePath == HybridWebView.ProxyRequestPath)
{
var args = new HybridWebViewProxyEventArgs(fullUrl);
var args = new HybridWebViewProxyEventArgs(fullUrl, method, requestHeaders);

await hwv.OnProxyRequestMessage(args);

if (args.ResponseStream != null)
{
contentType = args.ResponseContentType ?? "text/plain";
contentStream = args.ResponseStream;
responseHeaders = args.ResponseHeaders;
}
}

Expand All @@ -149,18 +164,18 @@ public async void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSche
{
using var ms = new MemoryStream();
contentStream.CopyTo(ms);
return (ms.ToArray(), contentType, StatusCode: 200);
return (ms.ToArray(), contentType, StatusCode: 200, responseHeaders);
}

var assetPath = Path.Combine(bundleRootDir, relativePath);

if (File.Exists(assetPath))
{
return (File.ReadAllBytes(assetPath), contentType, StatusCode: 200);
return (File.ReadAllBytes(assetPath), contentType, StatusCode: 200, responseHeaders);
}
}

return (Array.Empty<byte>(), ContentType: string.Empty, StatusCode: 404);
return (Array.Empty<byte>(), ContentType: string.Empty, StatusCode: 404, null);
}

[Export("webView:stopURLSchemeTask:")]
Expand Down
38 changes: 31 additions & 7 deletions HybridWebView/Platforms/Windows/HybridWebView.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ private async void CoreWebView2_WebResourceRequested(CoreWebView2 sender, CoreWe
using var deferral = eventArgs.GetDeferral();

var requestUri = QueryStringHelper.RemovePossibleQueryString(eventArgs.Request.Uri);
var method = eventArgs.Request.Method;
var headers = eventArgs.Request.Headers.ToDictionary(p => p.Key, p => p.Value);

if (new Uri(requestUri) is Uri uri && AppOriginUri.IsBaseOf(uri))
{
Expand All @@ -72,19 +74,21 @@ private async void CoreWebView2_WebResourceRequested(CoreWebView2 sender, CoreWe
}

Stream? contentStream = null;
IDictionary<string, string>? responseHeaders = null;

// Check to see if the request is a proxy request
if (relativePath == ProxyRequestPath)
{
var fullUrl = eventArgs.Request.Uri;

var args = new HybridWebViewProxyEventArgs(fullUrl);
var args = new HybridWebViewProxyEventArgs(fullUrl, method, headers);
await OnProxyRequestMessage(args);

if (args.ResponseStream != null)
{
contentType = args.ResponseContentType ?? "text/plain";
contentStream = args.ResponseStream;
responseHeaders = args.ResponseHeaders;
}
}

Expand All @@ -106,17 +110,21 @@ private async void CoreWebView2_WebResourceRequested(CoreWebView2 sender, CoreWe
Content: null,
StatusCode: 404,
ReasonPhrase: "Not Found",
Headers: GetHeaderString("text/plain", notFoundContent.Length)
Headers: GetHeaderString("text/plain", notFoundContent.Length, responseHeaders)
);
}
else
{
var randomStream = await CopyContentToRandomAccessStreamAsync(contentStream);

eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(
Content: await CopyContentToRandomAccessStreamAsync(contentStream),
Content: randomStream,
StatusCode: 200,
ReasonPhrase: "OK",
Headers: GetHeaderString(contentType, (int)contentStream.Length)
Headers: GetHeaderString(contentType, (int)randomStream.Size, responseHeaders)
);

randomStream = null;
}

contentStream?.Dispose();
Expand All @@ -135,9 +143,25 @@ async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream cont
}
}

private protected static string GetHeaderString(string contentType, int contentLength) =>
$@"Content-Type: {contentType}
Content-Length: {contentLength}";
private protected static string GetHeaderString(string contentType, int contentLength, IDictionary<string, string>? baseHeaders)
{
if (baseHeaders == null) baseHeaders = new Dictionary<string, string>();

if (baseHeaders.ContainsKey("Content-Type") == false)
{
baseHeaders["Content-Type"] = contentType;
}

if (baseHeaders.ContainsKey("Content-Length") == false)
{
baseHeaders["Content-Length"] = contentLength.ToString();
}

var valuesFormated = baseHeaders.Select(h => $"{h.Key}: {h.Value}");
var result = $@"{string.Join("\n", valuesFormated)}";

return result;
}

private void Wv2_WebMessageReceived(Microsoft.UI.Xaml.Controls.WebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
{
Expand Down
Loading