2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
fail-fast: false
matrix:
node-version:
# - 17
- 18
- 16
- 14
os:
Expand Down
2 changes: 1 addition & 1 deletion documentation/1-promise.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Source code: [`source/as-promise/index.ts`](../source/as-promise/index.ts)

The main Got function returns a [`Promise`](https://developer.mozilla.org/pl/docs/Web/JavaScript/Reference/Global_Objects/Promise).\
The main Got function returns a [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise).\
Although in order to support cancelation, [`PCancelable`](https://github.com/sindresorhus/p-cancelable) is used instead of pure `Promise`.

### <code>got(url: string | URL, options?: [OptionsInit](typescript.md#optionsinit), defaults?: [Options](2-options.md))</code>
Expand Down
4 changes: 3 additions & 1 deletion documentation/2-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ The most common methods are: `GET`, `HEAD`, `POST`, `PUT`, `DELETE`.
```js
import got from 'got';

const {method} = await got.post('https://httpbin.org/anything').json();
const {method} = await got('https://httpbin.org/anything', {
method: 'POST'
}).json();

console.log(method);
// => 'POST'
Expand Down
12 changes: 12 additions & 0 deletions documentation/3-streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,18 @@ The server's IP address.
Whether the response comes from cache or not.
### `ok`
**Type: `boolean`**
Whether the response was successful
**Note:**
> - A request is successful when the status code of the final request is `2xx` or `3xx`.
> - When [following redirects](2-options.md#followredirect), a request is successful **only** when the status code of the final request is `2xx`.
> - `304` responses are always considered successful.
> - Got throws automatically when `response.ok` is `false` and `throwHttpErrors` is `true`.
### `statusCode`
**Type: `number`**
Expand Down
4 changes: 1 addition & 3 deletions documentation/tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,12 @@ Got supports cookies out of box. There is no need to parse them manually.\
In order to use cookies, pass a `CookieJar` instance from the [`tough-cookie`](https://github.com/salesforce/tough-cookie) package.

```js
import {promisify} from 'node:util';
import got from 'got';
import {CookieJar} from 'tough-cookie';

const cookieJar = new CookieJar();
const setCookie = promisify(cookieJar.setCookie.bind(cookieJar));

await setCookie('foo=bar', 'https://httpbin.org');
await cookieJar.setCookie('foo=bar', 'https://httpbin.org');
await got('https://httpbin.org/anything', {cookieJar});
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "got",
"version": "12.0.3",
"version": "12.1.0",
"description": "Human-friendly and powerful HTTP request library for Node.js",
"license": "MIT",
"repository": "sindresorhus/got",
Expand Down
66 changes: 66 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,72 @@
<br>
<br>
<br>
<a href="https://neverinstall.com/spaces/devtools?utm_source=github&utm_medium=sponsor&utm_campaign=sindre#gh-light-mode-only">
<div>
<img src="https://sindresorhus.com/assets/thanks/neverinstall-logo-light.svg" width="200" alt="neverinstall">
</div>
<br>
<b>All your favourite IDE's now available on the cloud</b>
<div>
<sub>
Neverinstall gives you an uninterrupted development experience and improved accessibility,
<br>
allowing you to code faster, better and on-the-go on your favourite IDEs like
<br>
Android Studio, VS Code, Jupyter and PyCharm using your browser.
</sub>
</div>
</a>
<a href="https://neverinstall.com/spaces/devtools?utm_source=github&utm_medium=sponsor&utm_campaign=sindre#gh-dark-mode-only">
<div>
<img src="https://sindresorhus.com/assets/thanks/neverinstall-logo-dark.svg" width="200" alt="neverinstall">
</div>
<br>
<b>All your favourite IDE's now available on the cloud</b>
<div>
<sub>
Neverinstall gives you an uninterrupted development experience and improved accessibility,
<br>
allowing you to code faster, better and on-the-go on your favourite IDEs like
<br>
Android Studio, VS Code, Jupyter and PyCharm using your browser.
</sub>
</div>
</a>
<br>
<br>
<br>
<a href="https://www.useanvil.com/?utm_source=sindresorhus#gh-light-mode-only">
<div>
<img src="https://sindresorhus.com/assets/thanks/anvil-logo-light.svg" width="200" alt="Anvil">
</div>
<br>
<b>Paperwork that makes the data work.</b>
<div>
<sub>
Easy APIs for paperwork. PDF generation, e-signature and embeddable no-code webforms.
<br>
The easiest way to build paperwork automation into your product.
</sub>
</div>
</a>
<a href="https://www.useanvil.com/?utm_source=sindresorhus#gh-dark-mode-only">
<div>
<img src="https://sindresorhus.com/assets/thanks/anvil-logo-dark.svg" width="200" alt="Anvil">
</div>
<br>
<b>Paperwork that makes the data work.</b>
<div>
<sub>
Easy APIs for paperwork. PDF generation, e-signature and embeddable no-code webforms.
<br>
The easiest way to build paperwork automation into your product.
</sub>
</div>
</a>
<br>
<br>
<br>
<br>
</p>
<br>
Expand Down
75 changes: 14 additions & 61 deletions source/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import process from 'node:process';
import {Buffer} from 'node:buffer';
import {Duplex, Writable, Readable} from 'node:stream';
import {Duplex, Readable} from 'node:stream';
import {URL, URLSearchParams} from 'node:url';
import http, {ServerResponse} from 'node:http';
import type {ClientRequest, RequestOptions} from 'node:http';
Expand All @@ -23,6 +23,7 @@ import calculateRetryDelay from './calculate-retry-delay.js';
import Options, {OptionsError, OptionsInit} from './options.js';
import {isResponseOk, Response} from './response.js';
import isClientRequest from './utils/is-client-request.js';
import isUnixSocketURL from './utils/is-unix-socket-url.js';
import {
RequestError,
ReadError,
Expand Down Expand Up @@ -197,30 +198,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {

this._stopRetry = noop;

const unlockWrite = (): void => {
this._unlockWrite();
};

const lockWrite = (): void => {
this._lockWrite();
};

this.on('pipe', (source: Writable) => {
source.prependListener('data', unlockWrite);
source.on('data', lockWrite);

source.prependListener('end', unlockWrite);
source.on('end', lockWrite);
});

this.on('unpipe', (source: Writable) => {
source.off('data', unlockWrite);
source.off('data', lockWrite);

source.off('end', unlockWrite);
source.off('end', lockWrite);
});

this.on('pipe', source => {
if (source.headers) {
Object.assign(this.options.headers, source.headers);
Expand Down Expand Up @@ -259,13 +236,9 @@ export default class Request extends Duplex implements RequestEvents<Request> {
return;
}

const {json, body, form} = this.options;
if (json || body || form) {
this._lockWrite();
}

// Important! If you replace `body` in a handler with another stream, make sure it's readable first.
// The below is run only once.
const {body} = this.options;
if (is.nodeStream(body)) {
body.once('error', error => {
if (this._flushed) {
Expand Down Expand Up @@ -553,20 +526,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
return this;
}

private _lockWrite(): void {
const onLockedWrite = (): never => {
throw new TypeError('The payload has been already provided');
};

this.write = onLockedWrite;
this.end = onLockedWrite;
}

private _unlockWrite(): void {
this.write = super.write;
this.end = super.end;
}

private async _finalizeBody(): Promise<void> {
const {options} = this;
const {headers} = options;
Expand Down Expand Up @@ -639,10 +598,6 @@ export default class Request extends Duplex implements RequestEvents<Request> {
if (is.undefined(headers['content-length']) && is.undefined(headers['transfer-encoding']) && !cannotHaveBody && !is.undefined(uploadBodySize)) {
headers['content-length'] = String(uploadBodySize);
}
} else if (cannotHaveBody) {
this._lockWrite();
} else {
this._unlockWrite();
}

if (options.responseType === 'json' && !('accept' in options.headers)) {
Expand Down Expand Up @@ -678,6 +633,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
typedResponse.isFromCache = (this._nativeResponse as any).fromCache ?? false;
typedResponse.ip = this.ip;
typedResponse.retryCount = this.retryCount;
typedResponse.ok = isResponseOk(typedResponse);

this._isFromCache = typedResponse.isFromCache;

Expand Down Expand Up @@ -772,6 +728,11 @@ export default class Request extends Duplex implements RequestEvents<Request> {
const redirectBuffer = Buffer.from(response.headers.location, 'binary').toString();
const redirectUrl = new URL(redirectBuffer, url);

if (!isUnixSocketURL(url as URL) && isUnixSocketURL(redirectUrl)) {
this._beforeError(new RequestError('Cannot redirect to UNIX socket', {}, this));
return;
}

// Redirecting to a different site, clear sensitive data.
if (redirectUrl.hostname !== (url as URL).hostname || redirectUrl.port !== (url as URL).port) {
if ('host' in updatedOptions.headers) {
Expand Down Expand Up @@ -974,19 +935,11 @@ export default class Request extends Duplex implements RequestEvents<Request> {
this._beforeError(error);
}
})();
} else {
this._unlockWrite();

if (!is.undefined(body)) {
this._writeRequest(body, undefined, () => {});
currentRequest.end();

this._lockWrite();
} else if (this._cannotHaveBody || this._noPipe) {
currentRequest.end();

this._lockWrite();
}
} else if (!is.undefined(body)) {
this._writeRequest(body, undefined, () => {});
currentRequest.end();
} else if (this._cannotHaveBody || this._noPipe) {
currentRequest.end();
}
}

Expand Down
7 changes: 7 additions & 0 deletions source/core/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ export interface PlainResponse extends IncomingMessageWithTimings {
The result of the request.
*/
body?: unknown;

/**
Whether the response was successful.
__Note__: Got throws automatically when `response.ok` is `false` and `throwHttpErrors` is `true`.
*/
ok: boolean;
}

// For Promise support
Expand Down
6 changes: 6 additions & 0 deletions source/core/utils/is-unix-socket-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {URL} from 'url';

// eslint-disable-next-line @typescript-eslint/naming-convention
export default function isUnixSocketURL(url: URL) {
return url.protocol === 'unix:' || url.hostname === 'unix';
}
Loading