-
-
Save yusukebe/180bfcf261d142df4e233e90e65cb63b to your computer and use it in GitHub Desktop.
type MethodOverrideOptions = { | |
// Default is 'form' and the value is `_method` | |
form?: string | |
header?: string | |
query?: string | |
} | |
const DEFAULT_METHOD_FORM_NAME = '_method' | |
const methodOverride = (options?: MethodOverrideOptions): MiddlewareHandler => | |
async function methodOverride(c, next) { | |
// Method override by form | |
if (!options || options.form || !(options.form || options.header || options.query)) { | |
const methodFormName = options?.form || DEFAULT_METHOD_FORM_NAME | |
const contentType = c.req.header('content-type') | |
if (!(contentType === 'multipart/form-data' || contentType === 'application/x-www-form-urlencoded')) { | |
return await next() | |
} | |
const clonedRequest = c.req.raw.clone() | |
const newRequest = clonedRequest.clone() | |
const form = await clonedRequest.formData() | |
const method = form.get(methodFormName) | |
if (method) { | |
const newForm = await newRequest.formData() | |
newForm.delete(methodFormName) | |
const newHeaders = new Headers(clonedRequest.headers) | |
newHeaders.delete('content-type') | |
newHeaders.delete('content-length') | |
const request = new Request(c.req.url, { | |
body: newForm, | |
headers: newHeaders, | |
method | |
}) | |
return app.fetch(request, c.env, c.executionCtx) | |
} | |
} | |
// Method override by header | |
else if (options.header) { | |
const headerName = options.header | |
const method = c.req.header(headerName) | |
if (method) { | |
const newHeaders = new Headers(c.req.raw.headers) | |
newHeaders.delete(headerName) | |
const request = new Request(c.req.raw, { | |
headers: newHeaders, | |
method | |
}) | |
return app.fetch(request, c.env, c.executionCtx) | |
} | |
} | |
// Method override by query | |
else if (options.query) { | |
const queryName = options.query | |
const method = c.req.query(queryName) | |
if (method) { | |
const url = new URL(c.req.url) | |
url.searchParams.delete(queryName) | |
const request = new Request(url.toString(), { | |
body: c.req.raw.body, | |
headers: c.req.raw.headers, | |
method | |
}) | |
return app.fetch(request, c.env, c.executionCtx) | |
} | |
} | |
await next() | |
} |
Thanks @usualoma !
If you can override on GET, it may cause problems if you are using "SameSite=Lax" I think it is better not to override on GET.
That's right! I'll implement it as you said. For my use case, it's not a problem; it does not override on GET.
When used with CSRF middleware, depending on the order in which csrf and methodOverride are specified, if 'content-type' is dropped in methodOverride, the csrf middleware may slip through unintentionally.
I am removing the content-type
header because if its value is starting multipart/form-data
, the form content will not be created properly without removing it.
const form = new FormData()
form.append('foo', 'bar')
const req = new Request('http://localhost', {
method: 'POST',
body: form,
})
const newHeaders = new Headers(req.headers)
//newHeaders.delete('content-type')
const newForm = new FormData()
newForm.append('bar', 'baz')
const newReq = new Request('http://localhost', {
method: 'DELETE',
body: newForm,
headers: newHeaders,
})
// If we don't remove `content-type`, it will be `null`
console.log((await newReq.formData()).get('bar')) // null
However, if we create a Request with a new FormData instance in the body, the new content-type
is automatically added.
const form = new FormData()
form.append('foo', 'bar')
const req = new Request('http://localhost', {
method: 'POST',
body: form,
})
// multipart/form-data; boundary=----formdata-undici-008447172846
console.log(req.headers.get('content-type'))
const newHeaders = new Headers(req.headers)
newHeaders.delete('content-type')
const newForm = new FormData()
newForm.append('bar', 'baz')
const newReq = new Request('http://localhost', {
method: 'DELETE',
body: newForm,
headers: newHeaders,
})
// multipart/form-data; boundary=----formdata-undici-060073812549
console.log(newReq.headers.get('content-type'))
The following line needs to be modified, but if the content-type
starts with multipart/form-data
, we may remove it.
if (!(contentType === 'multipart/form-data' || contentType === 'application/x-www-form-urlencoded')) {
return await next()
}
Either way, I would like this feature and would like to make a PR.
Thanks for the explanation about the content-type, I understand. Thanks!
Created the PR! honojs/hono#2420
Hi @yusukebe
This is a surprisingly difficult middleware to implement. Here are some of my thoughts.