Skip to content

Commit

Permalink
Merge pull request #2454 from honojs/next
Browse files Browse the repository at this point in the history
Next
  • Loading branch information
yusukebe authored Apr 2, 2024
2 parents 98d4334 + 545203f commit eb97ee2
Show file tree
Hide file tree
Showing 54 changed files with 2,604 additions and 770 deletions.
Binary file modified bun.lockb
Binary file not shown.
40 changes: 26 additions & 14 deletions deno_dist/helper/ssg/ssg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ export interface ToSSGResult {
error?: Error
}

const generateFilePath = (routePath: string, outDir: string, mimeType: string) => {
const extension = determineExtension(mimeType)
const generateFilePath = (
routePath: string,
outDir: string,
mimeType: string,
extensionMap?: Record<string, string>
) => {
const extension = determineExtension(mimeType, extensionMap)

if (routePath.endsWith(`.${extension}`)) {
return joinPaths(outDir, routePath)
Expand Down Expand Up @@ -62,17 +67,22 @@ const parseResponseContent = async (response: Response): Promise<string | ArrayB
}
}

const determineExtension = (mimeType: string): string => {
switch (mimeType) {
case 'text/html':
return 'html'
case 'text/xml':
case 'application/xml':
return 'xml'
default: {
return getExtension(mimeType) || 'html'
}
export const defaultExtensionMap: Record<string, string> = {
'text/html': 'html',
'text/xml': 'xml',
'application/xml': 'xml',
'application/yaml': 'yaml',
}

const determineExtension = (
mimeType: string,
userExtensionMap?: Record<string, string>
): string => {
const extensionMap = userExtensionMap || defaultExtensionMap
if (mimeType in extensionMap) {
return extensionMap[mimeType]
}
return getExtension(mimeType) || 'html'
}

export type BeforeRequestHook = (req: Request) => Request | false | Promise<Request | false>
Expand All @@ -85,6 +95,7 @@ export interface ToSSGOptions {
afterResponseHook?: AfterResponseHook
afterGenerateHook?: AfterGenerateHook
concurrency?: number
extensionMap?: Record<string, string>
}

/**
Expand Down Expand Up @@ -204,14 +215,15 @@ const createdDirs: Set<string> = new Set()
export const saveContentToFile = async (
data: Promise<{ routePath: string; content: string | ArrayBuffer; mimeType: string } | undefined>,
fsModule: FileSystemModule,
outDir: string
outDir: string,
extensionMap?: Record<string, string>
): Promise<string | undefined> => {
const awaitedData = await data
if (!awaitedData) {
return
}
const { routePath, content, mimeType } = awaitedData
const filePath = generateFilePath(routePath, outDir, mimeType)
const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap)
const dirPath = dirname(filePath)

if (!createdDirs.has(dirPath)) {
Expand Down
3 changes: 3 additions & 0 deletions deno_dist/jsx/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useMemo,
useLayoutEffect,
useReducer,
useId,
useDebugValue,
} from '../hooks/index.ts'
import { Suspense, ErrorBoundary } from './components.ts'
Expand Down Expand Up @@ -69,6 +70,7 @@ export {
useMemo,
useLayoutEffect,
useReducer,
useId,
useDebugValue,
Suspense,
ErrorBoundary,
Expand All @@ -94,6 +96,7 @@ export default {
useMemo,
useLayoutEffect,
useReducer,
useId,
useDebugValue,
Suspense,
ErrorBoundary,
Expand Down
3 changes: 3 additions & 0 deletions deno_dist/jsx/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ export const useMemo = <T>(factory: () => T, deps: readonly unknown[]): T => {
return memoArray[hookIndex][0] as T
}

let idCounter = 0
export const useId = (): string => useMemo(() => `:r${(idCounter++).toString(32)}:`, [])

// Define to avoid errors. This hook currently does nothing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const useDebugValue = (_value: unknown, _formatter?: (value: unknown) => string): void => {}
3 changes: 3 additions & 0 deletions deno_dist/jsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useMemo,
useLayoutEffect,
useReducer,
useId,
useDebugValue,
} from './hooks/index.ts'
import { Suspense } from './streaming.ts'
Expand All @@ -34,6 +35,7 @@ export {
useRef,
useCallback,
useReducer,
useId,
useDebugValue,
use,
startTransition,
Expand All @@ -60,6 +62,7 @@ export default {
useRef,
useCallback,
useReducer,
useId,
useDebugValue,
use,
startTransition,
Expand Down
2 changes: 2 additions & 0 deletions deno_dist/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export * from './jsx/index.ts'
export * from './middleware/jsx-renderer/index.ts'
export { jwt } from './middleware/jwt/index.ts'
export * from './middleware/logger/index.ts'
export * from './middleware/method-override/index.ts'
export * from './middleware/powered-by/index.ts'
export * from './middleware/timing/index.ts'
export * from './middleware/pretty-json/index.ts'
export * from './middleware/secure-headers/index.ts'
export * from './middleware/trailing-slash/index.ts'
export * from './adapter/deno/serve-static.ts'
49 changes: 39 additions & 10 deletions deno_dist/middleware/basic-auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Context } from '../../context.ts'
import { HTTPException } from '../../http-exception.ts'
import type { HonoRequest } from '../../request.ts'
import type { MiddlewareHandler } from '../../types.ts'
Expand Down Expand Up @@ -26,31 +27,59 @@ const auth = (req: HonoRequest) => {
return { username: userPass[1], password: userPass[2] }
}

type BasicAuthOptions =
| {
username: string
password: string
realm?: string
hashFunction?: Function
}
| {
verifyUser: (username: string, password: string, c: Context) => boolean | Promise<boolean>
realm?: string
hashFunction?: Function
}

export const basicAuth = (
options: { username: string; password: string; realm?: string; hashFunction?: Function },
options: BasicAuthOptions,
...users: { username: string; password: string }[]
): MiddlewareHandler => {
if (!options) {
throw new Error('basic auth middleware requires options for "username and password"')
const usernamePasswordInOptions = 'username' in options && 'password' in options
const verifyUserInOptions = 'verifyUser' in options

if (!(usernamePasswordInOptions || verifyUserInOptions)) {
throw new Error(
'basic auth middleware requires options for "username and password" or "verifyUser"'
)
}

if (!options.realm) {
options.realm = 'Secure Area'
}
users.unshift({ username: options.username, password: options.password })

if (usernamePasswordInOptions) {
users.unshift({ username: options.username, password: options.password })
}

return async function basicAuth(ctx, next) {
const requestUser = auth(ctx.req)
if (requestUser) {
for (const user of users) {
const [usernameEqual, passwordEqual] = await Promise.all([
timingSafeEqual(user.username, requestUser.username, options.hashFunction),
timingSafeEqual(user.password, requestUser.password, options.hashFunction),
])
if (usernameEqual && passwordEqual) {
if (verifyUserInOptions) {
if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
await next()
return
}
} else {
for (const user of users) {
const [usernameEqual, passwordEqual] = await Promise.all([
timingSafeEqual(user.username, requestUser.username, options.hashFunction),
timingSafeEqual(user.password, requestUser.password, options.hashFunction),
])
if (usernameEqual && passwordEqual) {
await next()
return
}
}
}
}
const res = new Response('Unauthorized', {
Expand Down
28 changes: 20 additions & 8 deletions deno_dist/middleware/bearer-auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import type { Context } from '../../context.ts'
import { HTTPException } from '../../http-exception.ts'
import type { MiddlewareHandler } from '../../types.ts'
import { timingSafeEqual } from '../../utils/buffer.ts'

const TOKEN_STRINGS = '[A-Za-z0-9._~+/-]+=*'
const PREFIX = 'Bearer'

export const bearerAuth = (options: {
token: string | string[]
realm?: string
prefix?: string
hashFunction?: Function
}): MiddlewareHandler => {
if (!options.token) {
type BearerAuthOptions =
| {
token: string | string[]
realm?: string
prefix?: string
hashFunction?: Function
}
| {
realm?: string
prefix?: string
verifyToken: (token: string, c: Context) => boolean | Promise<boolean>
hashFunction?: Function
}

export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => {
if (!('token' in options || 'verifyToken' in options)) {
throw new Error('bearer auth middleware requires options for "token"')
}
if (!options.realm) {
Expand Down Expand Up @@ -49,7 +59,9 @@ export const bearerAuth = (options: {
throw new HTTPException(400, { res })
} else {
let equal = false
if (typeof options.token === 'string') {
if ('verifyToken' in options) {
equal = await options.verifyToken(match[1], c)
} else if (typeof options.token === 'string') {
equal = await timingSafeEqual(options.token, match[1], options.hashFunction)
} else if (Array.isArray(options.token) && options.token.length > 0) {
for (const token of options.token) {
Expand Down
39 changes: 36 additions & 3 deletions deno_dist/middleware/cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const cache = (options: {
cacheName: string
wait?: boolean
cacheControl?: string
vary?: string | string[]
}): MiddlewareHandler => {
if (!globalThis.caches) {
console.log('Cache Middleware is not enabled because caches is not defined.')
Expand All @@ -15,23 +16,55 @@ export const cache = (options: {
options.wait = false
}

const directives = options.cacheControl?.split(',').map((directive) => directive.toLowerCase())
const cacheControlDirectives = options.cacheControl
?.split(',')
.map((directive) => directive.toLowerCase())
const varyDirectives = Array.isArray(options.vary)
? options.vary
: options.vary?.split(',').map((directive) => directive.trim())
// RFC 7231 Section 7.1.4 specifies that "*" is not allowed in Vary header.
// See: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.4
if (options.vary?.includes('*')) {
throw new Error(
'Middleware vary configuration cannot include "*", as it disallows effective caching.'
)
}

const addHeader = (c: Context) => {
if (directives) {
if (cacheControlDirectives) {
const existingDirectives =
c.res.headers
.get('Cache-Control')
?.split(',')
.map((d) => d.trim().split('=', 1)[0]) ?? []
for (const directive of directives) {
for (const directive of cacheControlDirectives) {
let [name, value] = directive.trim().split('=', 2)
name = name.toLowerCase()
if (!existingDirectives.includes(name)) {
c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { append: true })
}
}
}

if (varyDirectives) {
const existingDirectives =
c.res.headers
.get('Vary')
?.split(',')
.map((d) => d.trim()) ?? []

const vary = Array.from(
new Set(
[...existingDirectives, ...varyDirectives].map((directive) => directive.toLowerCase())
)
).sort()

if (vary.includes('*')) {
c.header('Vary', '*')
} else {
c.header('Vary', vary.join(', '))
}
}
}

return async function cache(c, next) {
Expand Down
5 changes: 3 additions & 2 deletions deno_dist/middleware/cors/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Context } from '../../context.ts'
import type { MiddlewareHandler } from '../../types.ts'

type CORSOptions = {
origin: string | string[] | ((origin: string) => string | undefined | null)
origin: string | string[] | ((origin: string, c: Context) => string | undefined | null)
allowMethods?: string[]
allowHeaders?: string[]
maxAge?: number
Expand Down Expand Up @@ -36,7 +37,7 @@ export const cors = (options?: CORSOptions): MiddlewareHandler => {
c.res.headers.set(key, value)
}

const allowOrigin = findAllowOrigin(c.req.header('origin') || '')
const allowOrigin = findAllowOrigin(c.req.header('origin') || '', c)
if (allowOrigin) {
set('Access-Control-Allow-Origin', allowOrigin)
}
Expand Down
Loading

0 comments on commit eb97ee2

Please sign in to comment.