Repository: sindresorhus/normalize-url (js)

Issue:
Decoding entire pathname leads to edge case issues
The short of it is, if you have `%5C` in your URL string before normalizing it, you will end up with a `/` in the final result instead of a `\`. You can read my much lengthier explanation on [Ghost issue #20484](https://github.com/TryGhost/Ghost/issues/20484#issuecomment-2910906159).

I'll submit a PR shortly.

Pull request description:
Fix decoding entire pathname edge cases
Fixes #191 

One caveat with this change is that **all** invalid/reserved URI characters will now be encoded. This is necessary to prevent something like `\` from appearing and then v8 flipping that to a `/`. This URL is an example of how things go wrong with `%5C` being decoded to `\`, then getting flipped to `/` by the `URL` class. This is a valid URL that you can visit in your browser to see the image. 

https://tourpic-vector.maps.komoot.net/r/big/u%60t%5Be_pC~EzKnI%60p@h@nf@xFzJeB~EdQdXbH%60e@pHvOsAxp@lO%60VnA%7CVvyAhnAbYff@~P~ItT%5CjNjm@%7CQtU/?width=768&height=576&crop=true

However, if you run it through `normalize-url`, it will no longer map to the same resource on Komoot. Instead, you end up with this URL.

https://tourpic-vector.maps.komoot.net/r/big/u%60t[e_pC~EzKnI%60p@h@nf@xFzJeB~EdQdXbH%60e@pHvOsAxp@lO%60VnA%7CVvyAhnAbYff@~P~ItT/jNjm@%7CQtU/?width=768&height=576&crop=true

Patch:
diff --git a/index.js b/index.js
index 94e2b5b..54b3ec5 100644
--- a/index.js
+++ b/index.js
@@ -181,7 +181,7 @@ export default function normalizeUrl(urlString, options) {
 	// Decode URI octets
 	if (urlObject.pathname) {
 		try {
-			urlObject.pathname = decodeURI(urlObject.pathname);
+			urlObject.pathname = decodeURI(urlObject.pathname).replace(/\\/g, '%5C');
 		} catch {}
 	}
 


Test patch:
diff --git a/test.js b/test.js
index d934e89..ff09023 100644
--- a/test.js
+++ b/test.js
@@ -417,3 +417,11 @@ test('ignore custom schemes', t => {
 	t.is(normalizeUrl('sindre://www.sindresorhus.com'), 'sindre://www.sindresorhus.com');
 	t.is(normalizeUrl('foo:bar'), 'foo:bar');
 });
+
+test('encoded backslashes do not get decoded', t => {
+	t.is(normalizeUrl('https://foo.com/some%5Bthing%5Celse/that-is%40great@coding'), 'https://foo.com/some[thing%5Celse/that-is%40great@coding');
+	t.is(normalizeUrl('https://foo.com/something%5Celse/great'), 'https://foo.com/something%5Celse/great');
+
+	// Non-encoded backslashes should remain as-is.
+	t.is(normalizeUrl('https://foo.com/something\\else/great'), 'https://foo.com/something/else/great');
+});