@automattic/calypso-url
Version:
This library provides both generic and Calypso-specific utilities for handling URLs.
93 lines (76 loc) • 2.75 kB
text/typescript
import photon from 'photon';
import { getUrlParts, getUrlFromParts } from '../url-parts';
/**
* Pattern matching URLs to be left unmodified.
*/
let REGEX_EXEMPT_URL: RegExp;
if ( typeof globalThis.location === 'object' ) {
REGEX_EXEMPT_URL = new RegExp(
`^(/(?!/)|data:image/[^;]+;|blob:${ globalThis.location.origin }/)`
);
} else {
REGEX_EXEMPT_URL = /^(\/(?!\/)|data:image\/[^;]+;)/;
}
/**
* Pattern matching Automattic-controlled hostnames
*/
const REGEXP_A8C_HOST = /^([-a-zA-Z0-9_]+\.)*(gravatar\.com|wordpress\.com|wp\.com|a8c\.com)$/;
/**
* Query parameters to be treated as image dimensions
*/
const SIZE_PARAMS = [ 'w', 'h', 'resize', 'fit', 's' ];
/**
* Generate a safe version of the provided URL
*
* Images that Calypso uses have to be provided by a trusted TLS host. To do
* this, we check the host of the URL against a list of allowed hosts, and run the image
* through photon if the host name does not match.
*
* NOTE: This function will return `null` for external URLs with query strings,
* because Photon itself does not support this!
*
* @param url The URL to secure
* @returns The secured URL, or `null` if we couldn't make it safe
*/
export function safeImageUrl( url?: string | null ) {
if ( typeof url !== 'string' ) {
return null;
}
if ( url.length < 1 ) {
return null;
}
if ( REGEX_EXEMPT_URL.test( url ) ) {
return url;
}
const parsedUrl = getUrlParts( url );
if ( REGEXP_A8C_HOST.test( parsedUrl.hostname ) ) {
// Safely promote Automattic domains to HTTPS
parsedUrl.protocol = 'https';
return getUrlFromParts( parsedUrl ).toString();
}
// If there's a query string, bail out because Photon doesn't support them on external URLs
if ( parsedUrl.search && ( parsedUrl.host || parsedUrl.hostname || parsedUrl.origin ) ) {
// If it's just size parameters, let's remove them
SIZE_PARAMS.forEach( ( param ) => parsedUrl.searchParams.delete( param ) );
// Ditch authuser=0 if we find it - we see this on googleusercontent.com URLs and it doesn't affect the image output
if ( parsedUrl.searchParams.get( 'authuser' ) === '0' ) {
parsedUrl.searchParams.delete( 'authuser' );
}
// There are still parameters left, since they might be needed to retrieve the image, bail out
if ( Array.from( parsedUrl.searchParams ).length ) {
return null;
}
// Ensure we're creating a new URL without the size parameters
parsedUrl.search = '';
// Force https since if there's a missing protocol it'll throw an error
if ( ! parsedUrl?.protocol ) {
parsedUrl.protocol = 'https';
}
url = getUrlFromParts( parsedUrl ).toString();
}
// Photon doesn't support SVGs
if ( parsedUrl.pathname.endsWith( '.svg' ) ) {
return null;
}
return photon( url );
}