Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/adapter/aws-lambda/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export const handle = <E extends Env = Env, S extends Schema = {}, BasePath exte
? WithMultiValueHeaders
: WithHeaders)
>) => {
// @ts-expect-error FIXME: Fix return typing
// @ts-expect-error conditional return type is not inferable
return async (event, lambdaContext?) => {
const processor = getProcessor(event)

Expand Down
2 changes: 1 addition & 1 deletion src/middleware/combine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const every = (...middleware: (MiddlewareHandler | Condition)[]): Middlew
* @example
* ```ts
* import { except } from 'hono/combine'
* import { bearerAuth } from 'hono/bearer-auth
* import { bearerAuth } from 'hono/bearer-auth'
*
* // If client is accessing public API, then skip authentication.
* // Otherwise, require a valid token.
Expand Down
40 changes: 40 additions & 0 deletions src/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,22 @@ describe('Body methods with caching', () => {
expect(async () => await req.blob()).not.toThrow()
})

it('should parse form data even if formData() is called before parseBody()', async () => {
const data = new FormData()
data.append('foo', 'bar')
data.append('file', new File(['hello'], 't.txt', { type: 'text/plain' }))
const req = new HonoRequest(
new Request('http://localhost', {
method: 'POST',
body: data,
})
)
await req.formData()
const body = await req.parseBody()
expect(body['foo']).toBe('bar')
expect(body['file']).toBeInstanceOf(File)
})

describe('should not break body methods after parseBody() with non-form content-type', () => {
const createReq = () =>
new HonoRequest(
Expand Down Expand Up @@ -540,6 +556,30 @@ describe('cloneRawRequest', () => {
expect(req.raw, 'cloned request should contain the same properties').toMatchObject(clonedReq)
})

test('clones consumed multipart request with a matching boundary', async () => {
const data = new FormData()
data.append('foo', 'bar')
data.append('file', new File(['hello'], 't.txt', { type: 'text/plain' }))
const req = new HonoRequest(
new Request('http://localhost', {
method: 'POST',
body: data,
})
)
await req.formData()

const clonedReq = await cloneRawRequest(req)

const contentType = clonedReq.headers.get('Content-Type') ?? ''
const boundary = contentType.split('boundary=')[1]
const bodyText = await clonedReq.clone().text()
expect(bodyText.startsWith(`--${boundary}`)).toBe(true)

const formData = await clonedReq.formData()
expect(formData.get('foo')).toBe('bar')
expect(formData.get('file')).toBeInstanceOf(File)
})

test('clones GET request without body', async () => {
const req = new HonoRequest(
new Request('http://localhost', {
Expand Down
12 changes: 10 additions & 2 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,19 @@ export const cloneRawRequest = async (req: HonoRequest): Promise<Request> => {
})
}

const body = await req[cacheKey]()
const headers = req.header()
if (body instanceof FormData) {
// The FormData is re-serialized with a fresh multipart boundary, so the original
// Content-Type header no longer matches. Let the Request constructor generate it.
delete headers['content-type']
}

const requestInit: RequiredRequestInit = {
body: await req[cacheKey](),
body,
cache: req.raw.cache,
credentials: req.raw.credentials,
headers: req.header(),
headers,
integrity: req.raw.integrity,
keepalive: req.raw.keepalive,
method: req.method,
Expand Down
6 changes: 6 additions & 0 deletions src/utils/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ async function parseFormData<T extends BodyData>(
request: HonoRequest | Request,
options: ParseBodyOptions
): Promise<T> {
if (!isRawRequest(request) && request.bodyCache.formData) {
return convertFormDataToBodyData<T>(
await (request.bodyCache.formData as FormData | Promise<FormData>),
options
)
}
const headers = isRawRequest(request) ? request.headers : request.raw.headers
const arrayBuffer = await (request as Request).arrayBuffer()
const formDataPromise = bufferToFormData(arrayBuffer, headers.get('Content-Type') || '')
Expand Down
Loading