Skip to content

Commit

Permalink
fix(common): detect data: and blob: inputs in NgOptimizedImage
Browse files Browse the repository at this point in the history
…directive (#47082)

This commit updates the `NgOptimizedImage` directive to throw an error when `data:` and `blob:` inputs are used.

PR Close #47082
  • Loading branch information
AndrewKushnir authored and Pawel Kozlowski committed Aug 16, 2022
1 parent 0566205 commit 801daf8
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
42 changes: 42 additions & 0 deletions packages/common/src/directives/ng_optimized_image.ts
Expand Up @@ -30,6 +30,15 @@ export type ImageLoader = (config: ImageLoaderConfig) => string;
*/
const noopImageLoader = (config: ImageLoaderConfig) => config.src;

/**
* When a Base64-encoded image is passed as an input to the `NgOptimizedImage` directive,
* an error is thrown. The image content (as a string) might be very long, thus making
* it hard to read an error message if the entire string is included. This const defines
* the number of characters that should be included into the error message. The rest
* of the content is truncated.
*/
const BASE64_IMG_MAX_LENGTH_IN_ERROR = 50;

/**
* Special token that allows to configure a function that will be used to produce an image URL based
* on the specified input.
Expand Down Expand Up @@ -110,6 +119,8 @@ export class NgOptimizedImage implements OnInit {
ngOnInit() {
if (ngDevMode) {
assertExistingSrc(this);
assertNotBase64Image(this);
assertNotBlobURL(this);
}
this.setHostAttribute('loading', this.getLoadingBehavior());
this.setHostAttribute('fetchpriority', this.getFetchPriority());
Expand Down Expand Up @@ -181,3 +192,34 @@ function assertExistingSrc(dir: NgOptimizedImage) {
`the \`rawSrc\` to compute the final image URL and set the \`src\` itself.`);
}
}

// Verifies that the `rawSrc` is not a Base64-encoded image.
function assertNotBase64Image(dir: NgOptimizedImage) {
let rawSrc = dir.rawSrc.trim();
if (rawSrc.startsWith('data:')) {
if (rawSrc.length > BASE64_IMG_MAX_LENGTH_IN_ERROR) {
rawSrc = rawSrc.substring(0, BASE64_IMG_MAX_LENGTH_IN_ERROR) + '...';
}
throw new RuntimeError(
RuntimeErrorCode.INVALID_INPUT,
`The NgOptimizedImage directive detected that the \`rawSrc\` was set ` +
`to a Base64-encoded string (${rawSrc}). Base64-encoded strings are ` +
`not supported by the NgOptimizedImage directive. Use a regular \`src\` ` +
`attribute (instead of \`rawSrc\`) to disable the NgOptimizedImage ` +
`directive for this element.`);
}
}

// Verifies that the `rawSrc` is not a Blob URL.
function assertNotBlobURL(dir: NgOptimizedImage) {
const rawSrc = dir.rawSrc.trim();
if (rawSrc.startsWith('blob:')) {
throw new RuntimeError(
RuntimeErrorCode.INVALID_INPUT,
`The NgOptimizedImage directive detected that the \`rawSrc\` was set ` +
`to a blob URL (${rawSrc}). Blob URLs are not supported by the ` +
`NgOptimizedImage directive. Use a regular \`src\` attribute ` +
`(instead of \`rawSrc\`) to disable the NgOptimizedImage directive ` +
`for this element.`);
}
}
47 changes: 47 additions & 0 deletions packages/common/test/directives/ng_optimized_image_spec.ts
Expand Up @@ -104,6 +104,50 @@ describe('Image directive', () => {
'Please remove the `src` attribute from this image. The NgOptimizedImage directive will use ' +
'the `rawSrc` to compute the final image URL and set the `src` itself.');
});

it('should throw if `rawSrc` contains a Base64-encoded image (that starts with `data:`)', () => {
setupTestingModule();

expect(() => {
const template = '<img rawSrc="' + ANGULAR_LOGO_BASE64 + '" width="50" height="50">';
const fixture = createTestComponent(template);
fixture.detectChanges();
})
.toThrowError(
'NG02951: The NgOptimizedImage directive detected that the `rawSrc` was set ' +
'to a Base64-encoded string (' + ANGULAR_LOGO_BASE64.substring(0, 50) + '...). ' +
'Base64-encoded strings are not supported by the NgOptimizedImage directive. ' +
'Use a regular `src` attribute (instead of `rawSrc`) to disable the NgOptimizedImage ' +
'directive for this element.');
});

it('should throw if `rawSrc` contains a `blob:` URL', (done) => {
// Domino does not support canvas elements properly,
// so run this test only in a browser.
if (!isBrowser) {
done();
return;
}

const canvas = document.createElement('canvas');
canvas.toBlob(function(blob) {
const blobURL = URL.createObjectURL(blob!);

setupTestingModule();

// Note: use RegExp to partially match the error message, since the blob URL
// is created dynamically, so it might be different for each invocation.
const errorMessageRegExp =
/NG02951: The NgOptimizedImage directive detected that the `rawSrc` was set to a blob URL \(blob:/;
expect(() => {
const template = '<img rawSrc="' + blobURL + '" width="50" height="50">';
const fixture = createTestComponent(template);
fixture.detectChanges();
}).toThrowError(errorMessageRegExp);

done();
});
});
});

describe('lazy loading', () => {
Expand Down Expand Up @@ -159,6 +203,9 @@ describe('Image directive', () => {

// Helpers

const ANGULAR_LOGO_BASE64 =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==';

@Component({
selector: 'test-cmp',
template: '',
Expand Down

0 comments on commit 801daf8

Please sign in to comment.