Skip to content

Commit

Permalink
common: header casing preservation
Browse files Browse the repository at this point in the history
  • Loading branch information
Koushik Dutta authored and koush committed May 12, 2024
1 parent d3fbc58 commit 1e8126d
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 27 deletions.
10 changes: 5 additions & 5 deletions packages/auth-fetch/src/auth-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, fetcher, getFetchMethod, setDefaultHttpFetchAccept } from '../../../server/src/fetch';
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, createHeadersArray, fetcher, getFetchMethod, hasHeader, setDefaultHttpFetchAccept, setHeader } from '../../../server/src/fetch';

export interface AuthFetchCredentialState {
username: string;
Expand Down Expand Up @@ -74,15 +74,15 @@ export function createAuthFetch<B, M>(
) {
const authHttpFetch = async <T extends HttpFetchOptions<B>>(options: T & AuthFetchOptions): ReturnType<typeof h<T>> => {
const method = getFetchMethod(options);
const headers = new Headers(options.headers);
const headers = createHeadersArray(options.headers);
options.headers = headers;
setDefaultHttpFetchAccept(headers, options.responseType);

const initialHeader = await getAuth(options, options.url, method);
// try to provide an authorization if a session exists, but don't override Authorization if provided already.
// 401 will trigger a proper auth.
if (initialHeader && !headers.has('Authorization'))
headers.set('Authorization', initialHeader);
if (initialHeader && !hasHeader(headers, 'Authorization'))
setHeader(headers, 'Authorization', initialHeader);

const initialResponse = await h({
...options,
Expand Down Expand Up @@ -126,7 +126,7 @@ export function createAuthFetch<B, M>(

const header = await getAuth(options, options.url, method);
if (header)
headers.set('Authorization', header);
setHeader(headers, 'Authorization', header);

return h(options);
}
Expand Down
26 changes: 13 additions & 13 deletions server/src/fetch/http-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type events from 'events';
import type stream from 'stream';
import type followRedirects from 'follow-redirects';
import type { IncomingMessage } from 'http';
import type stream from 'stream';
import type { Readable } from 'stream';
import { HttpFetchBufferOptions, HttpFetchJsonOptions, HttpFetchOptions, HttpFetchReadableOptions, HttpFetchResponse, HttpFetchResponseType, HttpFetchTextOptions, checkStatus, createStringOrBufferBody, getFetchMethod, setDefaultHttpFetchAccept } from '.';
import { HttpFetchBufferOptions, HttpFetchJsonOptions, HttpFetchOptions, HttpFetchReadableOptions, HttpFetchResponse, HttpFetchResponseType, HttpFetchTextOptions, checkStatus, createHeadersArray, createStringOrBufferBody, getFetchMethod, setDefaultHttpFetchAccept } from '.';
export type { HttpFetchBufferOptions, HttpFetchJsonOptions, HttpFetchOptions, HttpFetchReadableOptions, HttpFetchResponse, HttpFetchResponseType, HttpFetchTextOptions, checkStatus, setDefaultHttpFetchAccept } from '.';

async function readMessageBuffer(response: IncomingMessage) {
Expand Down Expand Up @@ -64,7 +64,7 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
: T extends HttpFetchReadableOptions<Readable> ? IncomingMessage
: T extends HttpFetchJsonOptions<Readable> ? any : Buffer
>> {
const headers = new Headers(options.headers);
const headers = createHeadersArray(options.headers);
setDefaultHttpFetchAccept(headers, options.responseType);

const { once } = require('events') as typeof events;
Expand All @@ -83,16 +83,6 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
body = newBody;
}

const nodeHeaders: Record<string, string[]> = {};
for (const [k, v] of headers) {
if (nodeHeaders[k]) {
nodeHeaders[k].push(v);
}
else {
nodeHeaders[k] = [v];
}
}

let controller: AbortController;
let timeout: NodeJS.Timeout;
if (options.timeout) {
Expand All @@ -105,6 +95,16 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
const signal = controller?.signal || options.signal;
signal?.addEventListener('abort', () => request.destroy(new Error('abort')));

const nodeHeaders: Record<string, string[]> = {};
for (const [k, v] of headers) {
if (nodeHeaders[k]) {
nodeHeaders[k].push(v);
}
else {
nodeHeaders[k] = [v];
}
}

const request = proto.request(url, {
method: getFetchMethod(options),
rejectUnauthorized: options.rejectUnauthorized,
Expand Down
65 changes: 56 additions & 9 deletions server/src/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

export type HttpFetchResponseType = 'json' | 'text' | 'buffer' | 'readable';
export interface HttpFetchOptionsBase<B> {
url: string | URL;
Expand Down Expand Up @@ -73,15 +72,64 @@ export function getHttpFetchAccept(responseType: HttpFetchResponseType) {
return;
}

export function setDefaultHttpFetchAccept(headers: Headers, responseType: HttpFetchResponseType) {
if (headers.has('Accept'))
export function hasHeader(headers: [string, string][], key: string) {
key = key.toLowerCase();
return headers.find(([k]) => k.toLowerCase() === key);
}

export function removeHeader(headers: [string, string][], key: string) {
key = key.toLowerCase();
const filteredHeaders = headers.filter(([headerKey, _]) => headerKey.toLowerCase() !== key);
headers.length = 0;
filteredHeaders.forEach(header => headers.push(header));
}

export function setHeader(headers: [string, string][], key: string, value: string) {
removeHeader(headers, key);
headers.push([key, value]);
}

export function setDefaultHttpFetchAccept(headers: [string, string][], responseType: HttpFetchResponseType) {
if (hasHeader(headers, 'Accept'))
return;
const accept = getHttpFetchAccept(responseType);
if (accept)
headers.set('Accept', accept);
setHeader(headers, 'Accept', accept);
}

export function createHeadersArray(headers: HeadersInit): [string, string][] {
const headersArray: [string, string][] = [];
if (!headers)
return headersArray;
if (headers instanceof Headers) {
for (const [k, v] of headers.entries()) {
headersArray.push([k, v]);
}
return headersArray;
}

if (headers instanceof Array) {
for (const [k, v] of headers) {
headersArray.push([k, v]);
}
return headersArray;
}

for (const k of Object.keys(headers)) {
const v = headers[k];
headersArray.push([k, v]);
}

return headersArray;
}

export function createStringOrBufferBody(headers: Headers, body: any) {
/**
*
* @param headers
* @param body
* @returns Returns the body and Content-Type header that was set.
*/
export function createStringOrBufferBody(headers: [string, string][], body: any) {
let contentType: string;
if (typeof body === 'object') {
body = JSON.stringify(body);
Expand All @@ -91,9 +139,8 @@ export function createStringOrBufferBody(headers: Headers, body: any) {
contentType = 'text/plain';
}

if (!headers.has('Content-Type'))
headers.set('Content-Type', contentType);

if (!hasHeader(headers, 'Content-Type'))
setHeader(headers, 'Content-Type', contentType);
return body;
}

Expand All @@ -116,7 +163,7 @@ export async function domFetch<T extends HttpFetchOptions<BodyInit>>(options: T)
: T extends HttpFetchReadableOptions<BodyInit> ? Response
: T extends HttpFetchJsonOptions<BodyInit> ? any : Buffer
>> {
const headers = new Headers(options.headers);
const headers = createHeadersArray(options.headers);
setDefaultHttpFetchAccept(headers, options.responseType);

let { body } = options;
Expand Down

0 comments on commit 1e8126d

Please sign in to comment.