Skip to content

Commit f3f1014

Browse files
committed
Merge branch 'main' into feat/next-13-image
# Conflicts: # example/pages/index.tsx
2 parents 60f0dc4 + b941268 commit f3f1014

File tree

10 files changed

+152
-26
lines changed

10 files changed

+152
-26
lines changed

.github/workflows/codeql.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: "CodeQL"
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
schedule:
9+
- cron: "55 20 * * 1"
10+
11+
jobs:
12+
analyze:
13+
name: Analyze
14+
runs-on: ubuntu-latest
15+
permissions:
16+
actions: read
17+
contents: read
18+
security-events: write
19+
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
language: [ javascript ]
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v3
28+
29+
- name: Initialize CodeQL
30+
uses: github/codeql-action/init@v2
31+
with:
32+
languages: ${{ matrix.language }}
33+
queries: +security-and-quality
34+
35+
- name: Autobuild
36+
uses: github/codeql-action/autobuild@v2
37+
38+
- name: Perform CodeQL Analysis
39+
uses: github/codeql-action/analyze@v2
40+
with:
41+
category: "/language:${{ matrix.language }}"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ build
33
.github
44
.husky
55
.env
6+
7+
# Local Netlify folder
8+
.netlify

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"dev": "next dev",
77
"build": "next build",
8+
"build:ci": "NEXT_PUBLIC_UPLOADCARE_APP_BASE_URL=$DEPLOY_PRIME_URL NODE_ENV=production next build",
89
"start": "next start",
910
"lint": "next lint"
1011
},

example/pages/index.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
3535
<div className={styles.card}>
3636
<h1>
3737
Uploadcare custom loader for Image Component{' '}
38-
<a href="https://github.com/uploadcare/nextjs-loader">
38+
<a href="//github.com/uploadcare/nextjs-loader">
3939
@uploadcare/nextjs-loader
4040
</a>
4141
</h1>
@@ -50,7 +50,7 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
5050
<Image
5151
loader={uploadcareLoader}
5252
alt="Vercel logo"
53-
src="https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png"
53+
src="//ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png"
5454
width={500}
5555
height={500}
5656
sizes="(max-width: 50rem) 100vw, 50rem"
@@ -63,7 +63,7 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
6363
</p>
6464
<UploadcareImage
6565
alt="Vercel logo"
66-
src="https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png"
66+
src="//ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png"
6767
width={500}
6868
height={500}
6969
sizes="(max-width: 50rem) 100vw, 50rem"
@@ -94,7 +94,7 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
9494
<p>It will be proxied through Media Proxy.</p>
9595
<Image
9696
alt="Next.js logo"
97-
src="https://assets.vercel.com/image/upload/v1538361091/repositories/next-js/next-js.png"
97+
src="//assets.vercel.com/image/upload/v1538361091/repositories/next-js/next-js.png"
9898
width={500}
9999
height={166}
100100
loader={uploadcareLoader}
@@ -104,14 +104,14 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
104104
<p>SVGs and GIFs will be used without transformations</p>
105105
<Image
106106
alt="Next.js logo"
107-
src="https://ucarecdn.com/375bba4b-35db-4cb8-8fc7-7540625f2181/next.svg"
107+
src="//ucarecdn.com/375bba4b-35db-4cb8-8fc7-7540625f2181/next.svg"
108108
width={64}
109109
height={64}
110110
loader={uploadcareLoader}
111111
/>
112112
<Image
113113
alt="Vercel logo"
114-
src="https://ucarecdn.com/0f23a269-13eb-4fc9-b378-86f224380d26/vercel.gif"
114+
src="//ucarecdn.com/0f23a269-13eb-4fc9-b378-86f224380d26/vercel.gif"
115115
width={64}
116116
height={64}
117117
loader={uploadcareLoader}
@@ -140,7 +140,7 @@ const Home: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
140140
/>
141141
<hr className={styles.hr} />
142142
Checkout the project documentation on Github{' '}
143-
<a href="https://github.com/uploadcare/nextjs-loader">
143+
<a href="//github.com/uploadcare/nextjs-loader">
144144
@uploadcare/nextjs-loader
145145
</a>
146146
.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ensureUrlProtocol } from '../utils/helpers';
2+
3+
describe('ensureUrlProtocol', () => {
4+
it('should force https protocol for protocol relative URLs', () => {
5+
expect(ensureUrlProtocol('//domain.com')).toBe('https://domain.com');
6+
});
7+
8+
it('should not modify relative URLs', () => {
9+
expect(ensureUrlProtocol('/path/')).toBe('/path/');
10+
});
11+
12+
it('should not modify absolute URLs', () => {
13+
expect(ensureUrlProtocol('https://domain.com')).toBe('https://domain.com');
14+
expect(ensureUrlProtocol('http://domain.com')).toBe('http://domain.com');
15+
});
16+
});

src/__tests__/is-cdn-url.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import { isCdnUrl } from '../utils/helpers';
5+
6+
describe('isCdnUrl', () => {
7+
it('should return true in cases when provided URL matches provided domain', () => {
8+
expect(
9+
isCdnUrl('https://ucarecdn.com/:uuid/:filename', 'ucarecdn.com')
10+
).toBeTruthy();
11+
expect(isCdnUrl('http://ucarecdn.com/', 'ucarecdn.com')).toBeTruthy();
12+
expect(isCdnUrl('http://ucarecdn.com', 'ucarecdn.com')).toBeTruthy();
13+
expect(isCdnUrl('http://ucarecdn.com:8080', 'ucarecdn.com')).toBeTruthy();
14+
expect(
15+
isCdnUrl('http://custom.domain.com', 'custom.domain.com')
16+
).toBeTruthy();
17+
});
18+
19+
it("should return false in cases when provided URL doesn't matches provided domain", () => {
20+
expect(
21+
isCdnUrl('https://ucarecdn.com/:uuid/:filename', 'other.com')
22+
).toBeFalsy();
23+
expect(isCdnUrl('http://custom.domain.com', 'ucarecdn.com')).toBeFalsy();
24+
});
25+
26+
it('should throw a error if invalid URL provided', () => {
27+
expect(() => isCdnUrl('ucarecdn', 'ucarecdn.com')).toThrowError(
28+
/Invalid URL/
29+
);
30+
expect(() => isCdnUrl('//ucarecdn.com', 'ucarecdn.com')).toThrowError(
31+
/Invalid URL/
32+
);
33+
});
34+
});

src/__tests__/is-relative-url.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { isRelativeUrl } from '../utils/helpers';
2+
3+
describe('isRelativeUrl', () => {
4+
it('should return true when relative URL provided', () => {
5+
expect(isRelativeUrl('/relative/path/')).toBeTruthy();
6+
expect(isRelativeUrl('/')).toBeTruthy();
7+
});
8+
9+
it('should return false when non-relative URL provided', () => {
10+
expect(isRelativeUrl('http://domain.com')).toBeFalsy();
11+
expect(isRelativeUrl('//domain.com')).toBeFalsy();
12+
});
13+
});

src/__tests__/uploadcare-loader.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('uploadcareLoader', () => {
6767

6868
const t = () => {
6969
uploadcareLoader({
70-
src: '',
70+
src: 'https://example.com',
7171
width: 0,
7272
quality: 80
7373
});
@@ -95,7 +95,7 @@ describe('uploadcareLoader', () => {
9595
});
9696

9797
test('The loader parses user paramters properly', () => {
98-
const src = 'https:/example.com/image.jpg';
98+
const src = 'https://example.com/image.jpg';
9999

100100
addEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY', 'test-public-key');
101101

@@ -108,7 +108,7 @@ describe('uploadcareLoader', () => {
108108
});
109109

110110
expect(result).toBe(
111-
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/500x/-/quality/normal/https:/example.com/image.jpg'
111+
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/500x/-/quality/normal/https://example.com/image.jpg'
112112
);
113113

114114
// Override default params, including resize and quality.
@@ -125,7 +125,7 @@ describe('uploadcareLoader', () => {
125125
});
126126

127127
expect(result).toBe(
128-
'https://test-public-key.ucr.io/-/format/jpg/-/stretch/on/-/progressive/no/-/resize/1x/-/quality/smart_retina/https:/example.com/image.jpg'
128+
'https://test-public-key.ucr.io/-/format/jpg/-/stretch/on/-/progressive/no/-/resize/1x/-/quality/smart_retina/https://example.com/image.jpg'
129129
);
130130

131131
// Add a new parameter (no defaults).
@@ -142,15 +142,15 @@ describe('uploadcareLoader', () => {
142142
});
143143

144144
expect(result).toBe(
145-
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/500x/-/quality/normal/-/new/parameter/https:/example.com/image.jpg'
145+
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/500x/-/quality/normal/-/new/parameter/https://example.com/image.jpg'
146146
);
147147

148148
removeEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY');
149149
removeEnvVar('NEXT_PUBLIC_UPLOADCARE_TRANSFORMATION_PARAMETERS');
150150
});
151151

152152
test("The loader doesn't process SVG and GIF (absolute url)", () => {
153-
const src = 'https:/example.com/image.svg';
153+
const src = 'https://example.com/image.svg';
154154

155155
addEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY', 'test-public-key');
156156

@@ -242,7 +242,7 @@ describe('uploadcareLoader', () => {
242242

243243
// Not a jpg image. Should be max 3000 width.
244244

245-
let src = 'https:/example.com/image.png';
245+
let src = 'https://example.com/image.png';
246246

247247
addEnvVar(
248248
'NEXT_PUBLIC_UPLOADCARE_TRANSFORMATION_PARAMETERS',
@@ -256,7 +256,7 @@ describe('uploadcareLoader', () => {
256256
});
257257

258258
expect(result).toBe(
259-
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/3000x/-/quality/normal/https:/example.com/image.png'
259+
'https://test-public-key.ucr.io/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/3000x/-/quality/normal/https://example.com/image.png'
260260
);
261261

262262
// Jpg image by format. Should be max 5000 width.
@@ -273,13 +273,13 @@ describe('uploadcareLoader', () => {
273273
});
274274

275275
expect(result).toBe(
276-
'https://test-public-key.ucr.io/-/format/jpeg/-/stretch/off/-/progressive/yes/-/resize/5000x/-/quality/normal/https:/example.com/image.png'
276+
'https://test-public-key.ucr.io/-/format/jpeg/-/stretch/off/-/progressive/yes/-/resize/5000x/-/quality/normal/https://example.com/image.png'
277277
);
278278

279279
// // Jpg image by extension with auto format.
280280
// // Should be max 5000 width with /format/jpeg/ forced.
281281

282-
src = 'https:/example.com/image.jpg';
282+
src = 'https://example.com/image.jpg';
283283

284284
addEnvVar(
285285
'NEXT_PUBLIC_UPLOADCARE_TRANSFORMATION_PARAMETERS',
@@ -293,7 +293,7 @@ describe('uploadcareLoader', () => {
293293
});
294294

295295
expect(result).toBe(
296-
'https://test-public-key.ucr.io/-/format/jpeg/-/stretch/off/-/progressive/yes/-/resize/5000x/-/quality/normal/https:/example.com/image.jpg'
296+
'https://test-public-key.ucr.io/-/format/jpeg/-/stretch/off/-/progressive/yes/-/resize/5000x/-/quality/normal/https://example.com/image.jpg'
297297
);
298298

299299
removeEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY');

src/utils/helpers.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,26 @@ export function generateCustomProxyEndpoint(customProxyDomain: string): string {
113113
return `https://${customProxyDomain}`;
114114
}
115115

116-
export function isCdnUrl(url: string, cdnDomain: string): boolean {
117-
//eslint-disable-next-line
118-
const escapedCdnDomain = cdnDomain.replace('.', '.');
116+
function isProtocolRelativeUrl(url: string): boolean {
117+
return url.startsWith('//');
118+
}
119+
120+
export function isRelativeUrl(url: string): boolean {
121+
return url.startsWith('/') && !isProtocolRelativeUrl(url);
122+
}
119123

120-
//eslint-disable-next-line
121-
const regexp = new RegExp(`^https?:\/\/${escapedCdnDomain}`);
124+
export function ensureUrlProtocol(url: string): string {
125+
if (isProtocolRelativeUrl(url)) {
126+
return 'https:' + url;
127+
}
128+
return url;
129+
}
122130

123-
return regexp.test(url);
131+
export function isCdnUrl(url: string, cdnDomain: string): boolean {
132+
if (isRelativeUrl(url)) {
133+
return false;
134+
}
135+
return new URL(url).hostname === cdnDomain.trim();
124136
}
125137

126138
export function isProduction(): boolean {

src/utils/loader.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import {
1818
isProduction,
1919
mergeParams,
2020
parseUserParamsString,
21-
trimTrailingSlash
21+
trimTrailingSlash,
22+
isRelativeUrl,
23+
ensureUrlProtocol
2224
} from './helpers';
2325

2426
export function uploadcareLoader({
@@ -43,10 +45,11 @@ export function uploadcareLoader({
4345
process.env.NEXT_PUBLIC_UPLOADCARE_APP_BASE_URL || ''
4446
);
4547

48+
src = ensureUrlProtocol(src);
4649
const proxy = trimTrailingSlash(proxyEndpoint);
4750
const isProductionMode = isProduction();
4851
const isImageOnCdn = isCdnUrl(src, cdnDomain);
49-
const isImageRelative = src.startsWith('/');
52+
const isImageRelative = isRelativeUrl(src);
5053

5154
// Development mode; not on CDN.
5255
if (!isProductionMode && !isImageOnCdn) {
@@ -109,6 +112,9 @@ export function uploadcareLoader({
109112

110113
// Return the relative url AS IS if the base path is not set.
111114
if (!isBasePathSet) {
115+
console.warn(
116+
'Env variable "NEXT_PUBLIC_UPLOADCARE_APP_BASE_URL" is not set. You should set it to be able to serve local images.'
117+
);
112118
return src;
113119
}
114120

0 commit comments

Comments
 (0)