Repository: sindresorhus/got (ts)

Issue:
Got 9.0.0 throws when using unescaped characters
With Got 9.0.0,
```
require("got")("http://google.com/search", { query: { q: "friend’s house" } })
   .then(() => console.log("ok"))
```
rejects with:
```
(node:27956) UnhandledPromiseRejectionWarning: RequestError: Request path contains unescaped characters
    at EventEmitter.cacheReq.on.error (node_modules/got/source/request-as-event-emitter.js:105:27)
    at EventEmitter.emit (events.js:182:13)
    at makeRequest (node_modules/cacheable-request/src/index.js:125:9)
    at get (node_modules/cacheable-request/src/index.js:135:14)
    at process._tickCallback (internal/process/next_tick.js:68:7)
```

With `got@8.3.2` this snippet works just fine.

Pull request:
Follow redirects with encoded URI
Fixes #563

Patch:
diff --git a/package.json b/package.json
index 89dd886..55655eb 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
 		"decompress-response": "^3.3.0",
 		"duplexer3": "^0.1.4",
 		"get-stream": "^4.0.0",
-		"mimic-response": "^1.0.0",
+		"mimic-response": "^1.0.1",
 		"p-cancelable": "^0.5.0",
 		"to-readable-stream": "^1.0.0",
 		"url-parse-lax": "^3.0.0"
diff --git a/source/request-as-event-emitter.js b/source/request-as-event-emitter.js
index e92ed38..06deb36 100644
--- a/source/request-as-event-emitter.js
+++ b/source/request-as-event-emitter.js
@@ -74,7 +74,7 @@ module.exports = options => {
 				redirectUrl = (new URLGlobal(bufferString, urlLib.format(options))).toString();
 
 				try {
-					redirectUrl = decodeURI(redirectUrl);
+					decodeURI(redirectUrl);
 				} catch (error) {
 					emitter.emit('error', error);
 					return;


Test patch:
diff --git a/test/arguments.js b/test/arguments.js
index c24af46..1a3faaf 100644
--- a/test/arguments.js
+++ b/test/arguments.js
@@ -22,6 +22,10 @@ test.before('setup', async () => {
 		response.end(request.url);
 	});
 
+	s.on('/?test=it’s+ok', (request, response) => {
+		response.end(request.url);
+	});
+
 	s.on('/stream', (request, response) => {
 		response.end('ok');
 	});
@@ -99,6 +103,15 @@ test('overrides querystring from opts', async t => {
 	t.is(response.body, '/?test=wow');
 });
 
+test('escapes query parameter values', async t => {
+	const response = await got(`${s.url}`, {
+		query: {
+			test: `it’s ok`
+		}
+	});
+	t.is(response.body, '/?test=it%E2%80%99s+ok');
+});
+
 test('the `query` option can be a URLSearchParams', async t => {
 	const query = new URLSearchParams({test: 'wow'});
 	const {body} = await got(s.url, {query});
diff --git a/test/helpers/server.js b/test/helpers/server.js
index d0876e0..cf5abf2 100644
--- a/test/helpers/server.js
+++ b/test/helpers/server.js
@@ -14,7 +14,13 @@ exports.createServer = async () => {
 	const port = await getPort();
 
 	const s = http.createServer((request, response) => {
-		s.emit(request.url, request, response);
+		const event = decodeURI(request.url);
+		if (s.listeners(event).length === 0) {
+			response.writeHead(404, 'Not Found');
+			response.end(`No listener for ${event}`);
+		} else {
+			s.emit(event, request, response);
+		}
 	});
 
 	s.host = host;
diff --git a/test/redirects.js b/test/redirects.js
index 23bc96d..f56fb9c 100644
--- a/test/redirects.js
+++ b/test/redirects.js
@@ -7,6 +7,9 @@ let http;
 let https;
 
 test.before('setup', async () => {
+	const reached = (request, response) => {
+		response.end('reached');
+	};
 	https = await createSSLServer();
 	http = await createServer();
 
@@ -25,9 +28,7 @@ test.before('setup', async () => {
 
 	// HTTP Handlers
 
-	http.on('/', (request, response) => {
-		response.end('reached');
-	});
+	http.on('/', reached);
 
 	http.on('/finite', (request, response) => {
 		response.writeHead(302, {
@@ -36,9 +37,8 @@ test.before('setup', async () => {
 		response.end();
 	});
 
-	http.on('/utf8-url-áé', (request, response) => {
-		response.end('reached');
-	});
+	http.on('/utf8-url-áé', reached);
+	http.on('/?test=it’s+ok', reached);
 
 	http.on('/redirect-with-utf8-binary', (request, response) => {
 		response.writeHead(302, {
@@ -47,6 +47,13 @@ test.before('setup', async () => {
 		response.end();
 	});
 
+	http.on('/redirect-with-uri-encoded-location', (request, response) => {
+		response.writeHead(302, {
+			location: new URL('/?test=it’s+ok', http.url).toString()
+		});
+		response.end();
+	});
+
 	http.on('/endless', (request, response) => {
 		response.writeHead(302, {
 			location: `${http.url}/endless`
@@ -187,10 +194,14 @@ test('redirect response contains old url', async t => {
 	t.is(requestUrl, `${http.url}/finite`);
 });
 
-test('redirect response contains utf8 with binary encoding', async t => {
+test('redirect response contains UTF-8 with binary encoding', async t => {
 	t.is((await got(`${http.url}/redirect-with-utf8-binary`)).body, 'reached');
 });
 
+test('redirect response contains UTF-8 with URI encoding', async t => {
+	t.is((await got(`${http.url}/redirect-with-uri-encoded-location`)).body, 'reached');
+});
+
 test('throws on malformed redirect URI', async t => {
 	const error = await t.throwsAsync(got(`${http.url}/malformedRedirect`));
 	t.is(error.name, 'URIError');