diff --git a/docs/content/1.docs/2.features/blob.md b/docs/content/1.docs/2.features/blob.md index 53657d8c..4f0bed12 100644 --- a/docs/content/1.docs/2.features/blob.md +++ b/docs/content/1.docs/2.features/blob.md @@ -90,6 +90,8 @@ do { Returns a blob's data and sets `Content-Type`, `Content-Length` and `ETag` headers. +#### Image + ::code-group ```ts [server/routes/images/[...pathname\\].get.ts] export default eventHandler(async (event) => { @@ -105,6 +107,34 @@ export default eventHandler(async (event) => { ``` :: +#### Video + +::callout +Add `stream: true` option to prevent videos downloading. +:: + +::code-group +```ts [server/routes/videos/[...pathname\\].get.ts] +export default eventHandler(async (event) => { + const { pathname } = getRouterParams(event) + + return hubBlob().serve(event, pathname, { + stream: true + }) +}) +``` +```vue [pages/index.vue] + +``` +:: + ::important To prevent XSS attacks, make sure to control the Content type of the blob you serve. :: @@ -835,6 +865,14 @@ interface BlobListResult { } ``` +### `BlobServeOptions` + +```ts +interface BlobServeOptions { + stream: boolean +} +``` + ## Examples ### List blobs with pagination diff --git a/src/runtime/blob/server/utils/blob.ts b/src/runtime/blob/server/utils/blob.ts index 206d74af..85f335b1 100644 --- a/src/runtime/blob/server/utils/blob.ts +++ b/src/runtime/blob/server/utils/blob.ts @@ -8,7 +8,7 @@ import { defu } from 'defu' import { randomUUID } from 'uncrypto' import { parse } from 'pathe' import { joinURL } from 'ufo' -import type { BlobType, FileSizeUnit, BlobUploadedPart, BlobListResult, BlobMultipartUpload, HandleMPUResponse, BlobMultipartOptions, BlobUploadOptions, BlobPutOptions, BlobEnsureOptions, BlobObject, BlobListOptions, BlobCredentialsOptions, BlobCredentials } from '@nuxthub/core' +import type { BlobType, FileSizeUnit, BlobUploadedPart, BlobListResult, BlobMultipartUpload, HandleMPUResponse, BlobMultipartOptions, BlobUploadOptions, BlobPutOptions, BlobEnsureOptions, BlobObject, BlobListOptions, BlobCredentialsOptions, BlobCredentials, BlobServeOptions } from '@nuxthub/core' import { streamToArrayBuffer } from '../../../utils/stream' import { requireNuxtHubFeature } from '../../../utils/features' import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' @@ -56,7 +56,7 @@ interface HubBlob { * }) * ``` */ - serve(event: H3Event, pathname: string): Promise> + serve(event: H3Event, pathname: string, options?: BlobServeOptions): Promise> /** * Put a new blob into the bucket. * @@ -192,7 +192,23 @@ export function hubBlob(): HubBlob { folders: resolvedOptions.delimiter ? listed.delimitedPrefixes : undefined } }, - async serve(event: H3Event, pathname: string) { + async serve(event: H3Event, pathname: string, options?: BlobServeOptions) { + const resolvedOptions = defu(options, { + stream: false + }) + + if (resolvedOptions?.stream) { + const referrer = getHeader(event, 'referer') + const range = getHeader(event, 'range') + + if (!referrer || !range) { + throw createError({ + statusCode: 403, + message: 'Unauthorized' + }) + } + } + pathname = decodeURIComponent(pathname) const object = await bucket.get(pathname) diff --git a/src/types/blob.ts b/src/types/blob.ts index 450e46a5..6a58d5eb 100644 --- a/src/types/blob.ts +++ b/src/types/blob.ts @@ -262,3 +262,7 @@ export interface BlobCredentials { */ sessionToken: string } + +export interface BlobServeOptions { + stream: boolean +}