Skip to content

XMLHttpRequest replaces ContentType header from javascript #359

@svschouw-bb

Description

@svschouw-bb

We're having an issue where, especially under Windows, the browser uses very random mimetypes, depending on which application is registered to handle which file extensions. E.g., for a CSV file, the mime type can be text/plain if registered to a text editor, or application/vnd.ms-excel if it is registered to Excel. So in the javascript we correct these mimetypes based on the file extension. The filename itself is not sent to the server, so the server can't do the same logic.

We upload these files using XMLHttpRequest.send(File). We set the content type using xhr.setRequestHeader('Content-Type', contentType). This works fine in Firefox and Chrome, but not in HtmlUnit.

When doing an XMLHttpRequest.send(File) request, any Content-Type header set on the headers from the XMLHttpRequest object from javascript is ignored and replaced by the ContentType from the BrowserVersion.

HtmlUnit version: 2.50.0

HTML sample page:

<!DOCTYPE html>
<html>
<head>
<title>File upload test</title>
</head>
<body>
File: <input id="file" type="file"/><button id="submit">Submit</button><br/>
Result: <span id="result">Result goes here</span>
<script>
function onsubmit() {
	const fileInput = document.querySelector('#file');
	const xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function()
	{
		if(xhr.readyState === XMLHttpRequest.DONE) {
			const result = document.querySelector('#result');
			result.innerText = xhr.responseText;
		}
	}
	xhr.open('PUT', '/doupload');
	// We have analyzed the file(name), and we're sure it's actually a text/csv file
	xhr.setRequestHeader("Content-type", 'text/csv');
	xhr.send(fileInput.files[0]);
}

const submit = document.querySelector('#submit');
submit.addEventListener('click', onsubmit);
</script>
</body>
</html>

Spring Boot controller registered on /doupload:

	@PutMapping("/doupload")
	public @ResponseBody String handleUpload(@RequestHeader("Content-type") String contentType) {
		return contentType;
	}

HtmlUnit test that fails:

	@Test
	void testUpload() throws FailingHttpStatusCodeException, MalformedURLException, IOException {
		WebClient webClient = new WebClient();
		// Register a dummy mime type, to make the problem obvious
		webClient.getBrowserVersion().registerUploadMimeType("json", "foo/bar");

		HtmlPage page = webClient.getPage("http://localhost:8080/upload.html");
		HtmlFileInput file = page.querySelector("#file");
		// This line doesn't actually do anything for AJAX requests, only for multipart/form-data form submissions
		file.setContentType("application/json");
		file.setFiles(new File("src/test/resources/test.json"));
		HtmlButton submit = page.querySelector("#submit");
		submit.click();
		// Wait for all AJAX results to finish
		webClient.waitForBackgroundJavaScriptStartingBefore(1000);
		HtmlElement resultSpan = page.querySelector("#result");
		// #result now contains the Content-Type header of the PUT request
		// This fails with Expected: text/csv, actual: foo/bar
		assertEquals("text/csv", resultSpan.asNormalizedText());
		webClient.close();
	}

Tracing the code, my guess would be that Blob.fillRequest(...) should first check if there is already a Content-Type header present. Although I'm not sure how actual browsers implement this.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions