diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8cb5b785443..26f28eba590 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -8,6 +8,9 @@
+
+ Add basic support for parsing multipart/form-data fetch responses
+
evaluation of the proxy autoconf javascript code fixed (regression)
diff --git a/src/main/resources/org/htmlunit/javascript/polyfill/fetch/.editorconfig b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/.editorconfig
new file mode 100644
index 00000000000..99a1a0213ed
--- /dev/null
+++ b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/.editorconfig
@@ -0,0 +1,2 @@
+[fetch.umd.js]
+indent_size = 2
diff --git a/src/main/resources/org/htmlunit/javascript/polyfill/fetch/LICENSE b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/LICENSE
index 0e319d55ddc..d335b13b72e 100644
--- a/src/main/resources/org/htmlunit/javascript/polyfill/fetch/LICENSE
+++ b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/LICENSE
@@ -1,4 +1,5 @@
Copyright (c) 2014-2016 GitHub, Inc.
+Copyright (c) 2025 Jakob Ackermann
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/src/main/resources/org/htmlunit/javascript/polyfill/fetch/fetch.umd.js b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/fetch.umd.js
index 8c3269e3c72..952bc86e4d3 100644
--- a/src/main/resources/org/htmlunit/javascript/polyfill/fetch/fetch.umd.js
+++ b/src/main/resources/org/htmlunit/javascript/polyfill/fetch/fetch.umd.js
@@ -322,7 +322,14 @@
if (support.formData) {
this.formData = function() {
- return this.text().then(decode)
+ var body = this;
+ return this.text().then(function (text) {
+ var contentType = body.headers.get('content-type') || '';
+ if (contentType.indexOf('multipart/form-data') === 0) {
+ return parseMultipart(contentType, text);
+ }
+ return decode(text)
+ })
};
}
@@ -419,6 +426,56 @@
return form
}
+ /**
+ * @param header
+ * @param parameter
+ * @returns {string | undefined}
+ */
+ function parseHeaderParameter(header, parameter) {
+ var value
+ header.split(';').forEach(function (param) {
+ var keyVal = param.trim().split('=');
+ if (keyVal.length > 1 && keyVal[0] === parameter) {
+ value = keyVal[1];
+ if (value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
+ value = value.slice(1, value.length - 1);
+ }
+ }
+ })
+ return value
+ }
+
+ /**
+ * @param {string} contentType
+ * @param {string} text
+ * @returns {FormData}
+ */
+ function parseMultipart(contentType, text) {
+ var boundary = parseHeaderParameter(contentType, "boundary");
+ if (!boundary) {
+ throw new Error('missing multipart/form-data boundary parameter')
+ }
+ var prefix = '--' + boundary + '\r\n'
+ if (text.indexOf(prefix) !== 0) {
+ throw new Error('multipart/form-data body must start with --boundary')
+ }
+ var suffix = '\r\n--' + boundary + '--'
+ if (text.length < prefix.length + suffix.length || text.slice(text.length - suffix.length) !== suffix) {
+ throw new Error('multipart/form-data body must end with --boundary--')
+ }
+ var formData = new FormData();
+ text.slice(prefix.length, text.length - suffix.length).split('\r\n--' + boundary + '\r\n').forEach(function (part) {
+ var headersEnd = part.indexOf('\r\n\r\n');
+ if (headersEnd === -1) {
+ throw new Error('multipart/form-data part is missing headers')
+ }
+ var headers = parseHeaders(part.slice(0, headersEnd));
+ var name = parseHeaderParameter(headers.get('content-disposition'), 'name');
+ formData.append(name, part.slice(headersEnd + 4))
+ })
+ return formData
+ }
+
function parseHeaders(rawHeaders) {
var headers = new Headers();
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
diff --git a/src/test/java/org/htmlunit/javascript/host/fetch/FetchTest.java b/src/test/java/org/htmlunit/javascript/host/fetch/FetchTest.java
index cde68ed6748..8840501a9cd 100644
--- a/src/test/java/org/htmlunit/javascript/host/fetch/FetchTest.java
+++ b/src/test/java/org/htmlunit/javascript/host/fetch/FetchTest.java
@@ -505,6 +505,45 @@ public void fetchPostFormData() throws Exception {
.contains("HtmlUnit"));
}
+ /**
+ * @throws Exception if the test fails
+ */
+ @Test
+ @Disabled
+ @Alerts({"200", "OK", "true"})
+ public void fetchMultipartFormData() throws Exception {
+ final String html = DOCTYPE_HTML
+ + "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ final String content = "--0123456789\r\nContent-Disposition: form-data;name=test0\r\nContent-Type: text/plain\r\nHello1\nHello1\r\n--0123456789\r\nContent-Disposition: form-data;name=test1\r\nContent-Type: text/plain\r\nHello2\nHello2\r\n--0123456789--";
+ getMockWebConnection().setResponse(URL_SECOND, content, "multipart/form-data; boundary=0123456789");
+
+ final WebDriver driver = loadPage2(html);
+ verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
+
+ assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
+ }
+
/**
* @throws Exception if the test fails
*/