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
+}