Apparently Safari normalizes Unicode when sending POST data, while all other major browsers just send what they're given.
Normalization appears to be happening right before the data is sent over the wire, and using normalize() on the data doesn't work (Safari enforces NFC, regardless of what it's given).
This becomes a problem when requesting a filename with an accented character, which has different code points in NFC and NFD formats. The explanation essentially comes down to "combining characters" vs. "precomposed characters" in Unicode equivalence).
With that said, given an API that doesn't do its own normalization on the backend, and is expecting an array of strings (filenames), is it possible to send over the correct filename on the frontend when using Safari?
An example of the Unicode normalization problem:
const str = 'Rosé'
const nfc = str.normalize()
const nfd = str.normalize('NFD')
console.log(nfc === nfd) // false
console.log(nfc.codePointAt(3)) // 233
console.log(nfd.codePointAt(3)) // 101
console.log(nfc.codePointAt(4)) // undefined
console.log(nfd.codePointAt(4)) // 769
A minimal, reproducible example:
Note the console log differences between Chrome and Safari.
const isCorrectForm = (path, form) => path === path.normalize(`NF${form}`)
const fetchData = async() => {
const sourcePathC = '\u00e9'; // "é"
const sourcePathD = '\u0065\u0301'; // "é"
await fetch('https://httpbin.org/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
pathsFormC: [sourcePathC],
pathsFormD: [sourcePathD]
}),
})
.then((response) => response.json())
.then((data) => {
const responseData = JSON.parse(data.data);
const responsePathC = responseData.pathsFormC[0];
const responsePathD = responseData.pathsFormD[0];
console.log({
isSourcePathCFormC: isCorrectForm(sourcePathC, 'C'),
isSourcePathDFormD: isCorrectForm(sourcePathD, 'D'),
isResponsePathCFormC: isCorrectForm(responsePathC, 'C'),
isResponsePathDFormD: isCorrectForm(responsePathD, 'D'),
});
});
}
fetchData();
Update:
This is impossible to solve on the client-side alone. Either backend needs to handle normalization on the receiving end, or, as answered, the client needs to send the data encoded via encodeURIComponent
, in conjunction with backend implementing decoding of that data.