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 */