From 644384cf3621b273515d157f0af595f1c2e68f08 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Mon, 28 Jun 2021 12:15:35 +0430 Subject: [PATCH 1/6] fix(vercel): validate remote domains --- src/runtime/providers/vercel.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/runtime/providers/vercel.ts b/src/runtime/providers/vercel.ts index 4b91d043f..c6e8e2cf0 100644 --- a/src/runtime/providers/vercel.ts +++ b/src/runtime/providers/vercel.ts @@ -1,13 +1,22 @@ import { ProviderGetImage } from 'src' -import { stringifyQuery } from 'ufo' +import { stringifyQuery, hasProtocol } from 'ufo' // https://vercel.com/docs/more/adding-your-framework#images -export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, ctx) => { - const validWidths = Object.values(ctx.options.screens || {}).sort() +export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, { options: { domains = [], screens = {} } }) => { + const validWidths = Object.values(screens).sort() const largestWidth = validWidths[validWidths.length - 1] let width = Number(modifiers?.width || 0) + // Serve original src if domain is invalid + if (hasProtocol(src)) { + if (!domains.find((d: string) => src.startsWith(d))) { + return { + url: src + } + } + } + if (!width) { width = largestWidth if (process.env.NODE_ENV === 'development') { From 7d7e564b2fa77471affa248b11b6960cb4d4c81c Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Tue, 29 Jun 2021 14:15:34 +0430 Subject: [PATCH 2/6] fix: extract domain validation from providers --- src/runtime/image.ts | 15 +++++++++++++++ src/runtime/providers/ipx.ts | 12 ++---------- src/runtime/providers/vercel.ts | 13 ++----------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/runtime/image.ts b/src/runtime/image.ts index 505a78397..b8f5d166b 100644 --- a/src/runtime/image.ts +++ b/src/runtime/image.ts @@ -1,4 +1,5 @@ import defu from 'defu' +import { hasProtocol } from 'ufo' import type { ImageOptions, ImageSizesOptions, CreateImageOptions, ResolvedImage, MapToStatic, ImageCTX, $Img } from '../types/image' import { imageMeta } from './utils/meta' import { parseSize } from './utils' @@ -95,6 +96,20 @@ function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): Res } } + /** + * Externalize remote images if domain does not match with `domains` options + * + * @see https://image.nuxtjs.org/api/options#domains + */ + const domains = ctx.options.domains || [] + if (hasProtocol(input)) { + if (!domains.find((d: string) => input.startsWith(d))) { + return { + url: input + } + } + } + const { provider, defaults } = getProvider(ctx, options.provider || ctx.options.provider) const preset = getPreset(ctx, options.preset) diff --git a/src/runtime/providers/ipx.ts b/src/runtime/providers/ipx.ts index ffe56880e..c44bd71a2 100644 --- a/src/runtime/providers/ipx.ts +++ b/src/runtime/providers/ipx.ts @@ -1,5 +1,5 @@ import { ProviderGetImage } from 'src' -import { joinURL, encodeQueryItem, encodePath, hasProtocol } from 'ufo' +import { joinURL, encodeQueryItem, encodePath } from 'ufo' import { createOperationsGenerator } from '~image' const operationsGenerator = createOperationsGenerator({ @@ -16,7 +16,7 @@ const operationsGenerator = createOperationsGenerator({ formatter: (key, val) => encodeQueryItem(key, val) }) -export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = '/_ipx' } = {}, { options: { domains = [] } }) => { +export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = '/_ipx' } = {}) => { if (modifiers.width && modifiers.height) { modifiers.resize = `${modifiers.width}_${modifiers.height}` delete modifiers.width @@ -25,14 +25,6 @@ export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = '/_i const params = operationsGenerator(modifiers) - if (hasProtocol(src)) { - if (!domains.find((d: string) => src.startsWith(d))) { - return { - url: src - } - } - } - return { url: joinURL(baseURL, encodePath(src) + (params ? '?' + params : '')) } diff --git a/src/runtime/providers/vercel.ts b/src/runtime/providers/vercel.ts index c6e8e2cf0..d65f74b5d 100644 --- a/src/runtime/providers/vercel.ts +++ b/src/runtime/providers/vercel.ts @@ -1,22 +1,13 @@ import { ProviderGetImage } from 'src' -import { stringifyQuery, hasProtocol } from 'ufo' +import { stringifyQuery } from 'ufo' // https://vercel.com/docs/more/adding-your-framework#images -export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, { options: { domains = [], screens = {} } }) => { +export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, { options: { screens = {} } }) => { const validWidths = Object.values(screens).sort() const largestWidth = validWidths[validWidths.length - 1] let width = Number(modifiers?.width || 0) - // Serve original src if domain is invalid - if (hasProtocol(src)) { - if (!domains.find((d: string) => src.startsWith(d))) { - return { - url: src - } - } - } - if (!width) { width = largestWidth if (process.env.NODE_ENV === 'development') { From a0171aa58beeff56a6d2fab7d7edaf64ecf9b0e2 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 29 Jun 2021 18:20:56 +0200 Subject: [PATCH 3/6] feat: use hostname checking --- src/module.ts | 7 ++++++- src/provider.ts | 3 +-- src/runtime/image.ts | 13 +++++-------- src/types/image.ts | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/module.ts b/src/module.ts index a25d65b0b..bc51e1e97 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,6 +1,6 @@ import { resolve } from 'upath' import defu from 'defu' - +import { parseURL } from 'ufo' import type { Module } from '@nuxt/types' import { setupStaticGeneration } from './generate' import { resolveProviders, detectProvider } from './provider' @@ -34,6 +34,11 @@ const imageModule: Module = async function imageModule (moduleOpt const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults) + // Normalize domains to hostname + moduleOptions.domains = moduleOptions.domains + .map(domain => parseURL(domain, 'https://').host) + .filter(Boolean) as string[] + options.provider = detectProvider(options.provider, nuxt.options.target === 'static') options[options.provider] = options[options.provider] || {} diff --git a/src/provider.ts b/src/provider.ts index 171514caf..5f9f3d526 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,6 +1,5 @@ import { normalize, resolve, dirname } from 'upath' import { writeJson, mkdirp } from 'fs-extra' -import { parseURL } from 'ufo' import { hash } from './utils' import type { ModuleOptions, InputProvider, ImageModuleProvider, ProviderSetup } from './types' import { ipxSetup } from './ipx' @@ -31,7 +30,7 @@ export const providerSetup: Record = { const imagesConfig = resolve(nuxt.options.rootDir, '.vercel_build_output/config/images.json') await mkdirp(dirname(imagesConfig)) await writeJson(imagesConfig, { - domains: moduleOptions.domains.map(domain => parseURL(domain, 'https://').host), + domains: moduleOptions.domains, sizes: Array.from(new Set(Object.values(moduleOptions.screens || {}))) }) } diff --git a/src/runtime/image.ts b/src/runtime/image.ts index b8f5d166b..375f2b82a 100644 --- a/src/runtime/image.ts +++ b/src/runtime/image.ts @@ -1,5 +1,5 @@ import defu from 'defu' -import { hasProtocol } from 'ufo' +import { hasProtocol, parseURL } from 'ufo' import type { ImageOptions, ImageSizesOptions, CreateImageOptions, ResolvedImage, MapToStatic, ImageCTX, $Img } from '../types/image' import { imageMeta } from './utils/meta' import { parseSize } from './utils' @@ -96,14 +96,11 @@ function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): Res } } - /** - * Externalize remote images if domain does not match with `domains` options - * - * @see https://image.nuxtjs.org/api/options#domains - */ - const domains = ctx.options.domains || [] + // Externalize remote images if domain does not match with `domains` if (hasProtocol(input)) { - if (!domains.find((d: string) => input.startsWith(d))) { + const inputHost = parseURL(input).host + // Domains are normalized to hostname in module + if (!ctx.options.domains.find(d => d === inputHost)) { return { url: input } diff --git a/src/types/image.ts b/src/types/image.ts index 30669cf79..1e015b0ee 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -35,7 +35,7 @@ export interface CreateImageOptions { presets: { [name: string]: ImageOptions } provider: string screens?: Record, - domains?: string[] + domains: string[] } export interface ImageInfo { From 9932670cac131dbc6c9f4d4fb87bd2afd7506f02 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 29 Jun 2021 18:21:52 +0200 Subject: [PATCH 4/6] fix typo --- src/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.ts b/src/module.ts index bc51e1e97..910a4849e 100644 --- a/src/module.ts +++ b/src/module.ts @@ -35,7 +35,7 @@ const imageModule: Module = async function imageModule (moduleOpt const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults) // Normalize domains to hostname - moduleOptions.domains = moduleOptions.domains + options.domains = options.domains .map(domain => parseURL(domain, 'https://').host) .filter(Boolean) as string[] From a970cf58a2fb62662d17f41d1667ac8ef1184361 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 29 Jun 2021 18:31:32 +0200 Subject: [PATCH 5/6] use validateDomains flag --- src/runtime/image.ts | 8 ++++---- src/runtime/providers/ipx.ts | 2 ++ src/runtime/providers/vercel.ts | 2 ++ src/types/image.ts | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/runtime/image.ts b/src/runtime/image.ts index 375f2b82a..eca37fad6 100644 --- a/src/runtime/image.ts +++ b/src/runtime/image.ts @@ -96,8 +96,11 @@ function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): Res } } + const { provider, defaults } = getProvider(ctx, options.provider || ctx.options.provider) + const preset = getPreset(ctx, options.preset) + // Externalize remote images if domain does not match with `domains` - if (hasProtocol(input)) { + if (provider.validateDomains && hasProtocol(input)) { const inputHost = parseURL(input).host // Domains are normalized to hostname in module if (!ctx.options.domains.find(d => d === inputHost)) { @@ -107,9 +110,6 @@ function resolveImage (ctx: ImageCTX, input: string, options: ImageOptions): Res } } - const { provider, defaults } = getProvider(ctx, options.provider || ctx.options.provider) - const preset = getPreset(ctx, options.preset) - const _options: ImageOptions = defu(options, preset, defaults) _options.modifiers = { ..._options.modifiers } const expectedFormat = _options.modifiers.format diff --git a/src/runtime/providers/ipx.ts b/src/runtime/providers/ipx.ts index c44bd71a2..eecac935d 100644 --- a/src/runtime/providers/ipx.ts +++ b/src/runtime/providers/ipx.ts @@ -29,3 +29,5 @@ export const getImage: ProviderGetImage = (src, { modifiers = {}, baseURL = '/_i url: joinURL(baseURL, encodePath(src) + (params ? '?' + params : '')) } } + +export const validateDomains = true diff --git a/src/runtime/providers/vercel.ts b/src/runtime/providers/vercel.ts index d65f74b5d..f08d51075 100644 --- a/src/runtime/providers/vercel.ts +++ b/src/runtime/providers/vercel.ts @@ -34,3 +34,5 @@ export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel }) } } + +export const validateDomains = true diff --git a/src/types/image.ts b/src/types/image.ts index 1e015b0ee..5faf341f7 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -23,6 +23,7 @@ export type ProviderGetImage = (src: string, options: ImageOptions, ctx: ImageCT export interface ImageProvider { defaults?: any getImage: ProviderGetImage + validateDomains?: Boolean } export interface CreateImageOptions { From f117bd78f6f62e95f3eb4c626bae76fcbd13aa01 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 29 Jun 2021 18:36:05 +0200 Subject: [PATCH 6/6] revert unrelated change --- src/runtime/providers/vercel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/providers/vercel.ts b/src/runtime/providers/vercel.ts index f08d51075..68e6b02a1 100644 --- a/src/runtime/providers/vercel.ts +++ b/src/runtime/providers/vercel.ts @@ -3,8 +3,8 @@ import { stringifyQuery } from 'ufo' // https://vercel.com/docs/more/adding-your-framework#images -export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, { options: { screens = {} } }) => { - const validWidths = Object.values(screens).sort() +export const getImage: ProviderGetImage = (src, { modifiers, baseURL = '/_vercel/image' } = {}, ctx) => { + const validWidths = Object.values(ctx.options.screens || {}).sort() const largestWidth = validWidths[validWidths.length - 1] let width = Number(modifiers?.width || 0)