MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
452 stars 55 forks source link

CoreWebView2_WebResourceRequested not providing binary multi-part POST Data in Request.Content #2162

Closed RickStrahl closed 2 weeks ago

RickStrahl commented 2 years ago

I have an application that captures Web Request data for later playback (WebSurge). This works great for standard text based content be it API requests or captured form data - the data gets captured into the Request stream and I can grab it out of there. Works great.

However, if the data is multi-part form data with binary data, the Request.Content is always null:

image

In contrast, here is a POST request with standard form data which does work and returns a Request.Content stream:

image

So for some reason it looks if the data is multi-part or contains binary data the request content is not provided.

AB#38186390

champnic commented 2 years ago

Thanks for the bug report @RickStrahl! I've added this to our backlog.

RickStrahl commented 2 years ago

Is there any word on whether this will be addressed?

Tested today with latest version and found this still doesn't work. Confirmed that:

RickStrahl commented 2 years ago

@champnic Ping! Any progress or at least confirmation that this is a bug and will get addressed in the future? Or a workaround?

champnic commented 2 years ago

Thanks for the ping Rick. @yildirimcagri do you have any info on this issue?

SharpEnvelope commented 1 year ago

I also ran into exactly this issue. Ran into it when using WebView2 from C++ but also reproduced it in C# WinForms.

This can be reproduced with the following JavaScript code:

const obj = { hello: "world" };
const blob = new Blob([JSON.stringify(obj, null, 2)], {
    type: "application/json",
});

const data = new FormData();
data.append("val1", "val1");
data.append("blob", blob);

const req = new XMLHttpRequest();
req.addEventListener("load", () => {
    alert("Received: " + req.responseText);
});
req.open("POST", "https://post.asset/");
req.send(data);

And the following C# code:

this.webView2Control.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
this.webView2Control.CoreWebView2.AddWebResourceRequestedFilter("https://post.asset/", CoreWebView2WebResourceContext.All);

private void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs eventArgs)
{
    System.IO.Stream content = eventArgs.Request.Content;

    if(content != null)
    {
        byte[] bytes = new byte[content.Length + 10];
        content.Read(bytes, 0, (int)content.Length);

        var stringData = System.Text.Encoding.Default.GetString(bytes);
        MessageBox.Show("Received: " + stringData);
    }
    else
    {
        MessageBox.Show("No content stream :(");
    }

    eventArgs.Response = this.webView2Control.CoreWebView2.Environment.CreateWebResourceResponse(null, 200, "OK", "");
}

The attached solution contains a running sample: WebView2BinaryResourceIssue.zip

yildirimcagri-msft commented 1 year ago

Thanks for the sample code for this. I missed the earlier ping about this, but I will prioritize this issue now.

stffabi commented 1 year ago

Seeing the same behaviour in a Wails application.

RickStrahl commented 1 year ago

Any update on this @yildirimcagri? @champnic?

SharpEnvelope commented 1 year ago

Are there any plans to address this issue @yildirimcagri-msft?

visuall commented 1 year ago

Same for me. The WebResourceRequested handler is the only way to intercept/filter requests and this bug makes this impossible. @yildirimcagri-msft , you say you will prioritize this issue for the last year or so and still nothing. Will it be possible finally this issue to be solved ?

bogerj commented 1 year ago

I am also the same. This issue has caused my solution to be unable to proceed due to this bug. Will this issue be resolved? Looking forward to your reply

steno916 commented 11 months ago

@champnic @yildirimcagri-msft Is there any status update on this? Is there a workaround? Thanks.

MarcoB89 commented 8 months ago

We are in the same situation, any updates? Is there a workaround to avoid this problem?

Yuxiza commented 8 months ago

Is there any updates? Or some alternatives?

Yuxiza commented 8 months ago

Here is a temporary solution:

const formData= new FormData();
formData.append("val1", "val1");
formData.append("blob", blob);
const req = new Request(uploadUrl, {
      method: "POST",
      body: formData,
});
const buffer = await req.arrayBuffer();
const headers = req.headers;
const response = await fetch(uploadUrl,{ 
    method:"POST",
    body:buffer,
    headers
});

By transmut multi-part data into arrayBuffer, we can access the request body though Request.Content.

MarcoB89 commented 8 months ago

Here is a temporary solution:

const formData= new FormData();
formData.append("val1", "val1");
formData.append("blob", blob);
const req = new Request(uploadUrl, {
      method: "POST",
      body: formData,
});
const buffer = await req.arrayBuffer();
const headers = req.headers;
const response = await fetch(uploadUrl,{ 
    method:"POST",
    body:buffer,
    headers
});

By transmut multi-part data into arrayBuffer, we can access the request body though Request.Content.

Seems this workaround works, but not in my scenario, I'm appending to the body not a blob but a file from an input type file for upload it. Using arrayBuffer() the file is loaded in memory, and for upload of large files that's not good...

MarcoB89 commented 8 months ago

Any updates? This bug caused a complete reengineering of our solution. It's not possible do a multi-part POST request for uploading file!

victorhuangwq commented 8 months ago

From what I can see, this is being worked on. We will post any updates here.

leonidukg commented 7 months ago

Been watching this bug for a long time too. But this bug is not in Webview2, it is Chromium itself that has this problem. It can be seen in Devtools.

Even if you connect via DevTools Protocol FetchRequestPaused, the Post request will be empty.

BUT!

I found a workaround. The full Post request can be seen through:

DevTools Protocol Network.requestWillBeSent

It's a miracle! Next is a simple code:

  1. protocol initialization:

    private Dictionary<string, string> HelpPostNetworkData = new();
    
        private async void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
        {
            await WebView.CoreWebView2.CallDevToolsProtocolMethodAsync("Network.enable", "{}");
            WebView.CoreWebView2.GetDevToolsProtocolEventReceiver("Network.requestWillBeSent").DevToolsProtocolEventReceived += WebView2_NetworkData;
    }
  2. Processing our data and saving the Postdata to the backup dictionary

        private async void WebView2_NetworkData(object sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs e)
        {

            if (e == null || e.ParameterObjectAsJson == null)
            {
                return;
            }

            var doc = JsonDocument.Parse(e.ParameterObjectAsJson);

            string payload = "{\"requestId\":\"" + doc.RootElement.GetProperty("requestId") + "\"}";
            string bodyResponse;
            string nowurl = doc.RootElement.GetProperty("request").GetProperty("url").ToString().ToLower();

            if (doc.RootElement.GetProperty("request").GetProperty("method").ToString().Equals("post", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    bodyResponse = await WebView.CoreWebView2.CallDevToolsProtocolMethodAsync("Network.getRequestPostData", payload);
                }
                catch 
                {
                    return;
                }

                if (!string.IsNullOrEmpty(bodyResponse))
                {
                    HelpPostNetworkData.Remove(nowurl);
                    HelpPostNetworkData.Add(nowurl, bodyResponse);
                }

            }
        }

And now you can use this dictionary to work and check if Content == null in CoreWebView2WebResourceResponseReceived, then take data from the dictionary at the URL

bob-dawson commented 7 months ago

I tried leonidukg's comment. It works, but when the post data include file, the file values will omit. So, it still need to fix.

leonidukg commented 7 months ago

I tried leonidukg's comment. It works, but when the post data include file, the file values will omit. So, it still need to fix.

You are right because Network.requestWillBeSent is executed BEFORE it sends all the data. And most likely the file download is not included in this check. I only need the field names to work, the values are not important.

p.s. I have corrected my code to prevent requests from hanging by accident.

bob-dawson commented 4 months ago

I tried leonidukg's comment. It works, but when the post data include file, the file values will omit. So, it still need to fix.

You are right because Network.requestWillBeSent is executed BEFORE it sends all the data. And most likely the file download is not included in this check. I only need the field names to work, the values are not important.

p.s. I have corrected my code to prevent requests from hanging by accident.

I mean file upload. you can't get upload file content.

leonidukg commented 2 months ago

Once again encountered this problem and even my workaround won't help. So I had to look for another one, but I already had a function to modify HTML code and I was surprised to see PostData perfectly through this function. So who needs not just POST variables, but their content, use it:

private Dictionary<string, string> HelpPostNetworkData = new();

        private async void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
        {
            WebView.CoreWebView2.GetDevToolsProtocolEventReceiver("Fetch.requestPaused").DevToolsProtocolEventReceived += WebView2_FetchRequestPaused;
            await WebView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.enable", "{\"patterns\":[{\"urlPattern\":\"*\", \"requestStage\":\"Response\"}]}");
}

        private async void WebView2_NetworkData(object sender, CoreWebView2DevToolsProtocolEventReceivedEventArgs e)
        {

            if (e == null || e.ParameterObjectAsJson == null)
            {
                return;
            }

            var doc = JsonDocument.Parse(e.ParameterObjectAsJson);

            string payload = "{\"requestId\":\"" + doc.RootElement.GetProperty("requestId") + "\"}";
            string nowurl = doc.RootElement.GetProperty("request").GetProperty("url").ToString().ToLower();

            if (doc.RootElement.TryGetProperty("responseHeaders", out JsonElement JSHeadersPost))
            {
                if (!JSHeadersPost.ToString().Contains("application/json", StringComparison.OrdinalIgnoreCase))
                {
                    if (doc.RootElement.GetProperty("request").GetProperty("method").ToString().Equals("post", StringComparison.OrdinalIgnoreCase))
                    {
                        if (doc.RootElement.GetProperty("request").TryGetProperty("postData", out JsonElement JSbodyResponse))
                        {
                                HelpPostNetworkData.Remove(nowurl);
                                HelpPostNetworkData.Add(nowurl, JSbodyResponse.ToString());
                        }
                    }
                }
            }
await Application.Current.Dispatcher.InvokeAsync(() => { try { WebView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.continueRequest", payload); } catch { } });
        }
yildirimcagri-msft commented 2 weeks ago

Hello, this should be working now. Please reactivate if it is not.

RickStrahl commented 2 weeks ago

As of which release?

RickStrahl commented 2 weeks ago

So I'm looking at this in the latest release build and I'm not sure what I'm looking at or how I'm supposed to get at this data.

It looks like Request content returns some sort of stream, but the stream is way too small - it doesn't contain the upload data:

image

and indeed when I try to read the actual file content it's always empty. I do get the headers now.

Here's what I do:

That works to give me the variables - content type, length of content, filename. But the data is always 0 length which makes sense since the incoming data does not reflect the full body that includes the full binary file size.

image

From the looks of it the payload captured is only the non-binary header data.


Using latest release SDK 1.0.2849.39