Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/html/safeurl.js
4069 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview The SafeUrl type and its builders.
9
*
10
* TODO(xtof): Link to document stating type contract.
11
*/
12
13
goog.provide('goog.html.SafeUrl');
14
15
goog.require('goog.asserts');
16
goog.require('goog.fs.url');
17
goog.require('goog.html.TrustedResourceUrl');
18
goog.require('goog.string.Const');
19
goog.require('goog.string.TypedString');
20
goog.require('goog.string.internal');
21
goog.require('goog.utils');
22
23
24
25
/**
26
* A string that is safe to use in URL context in DOM APIs and HTML documents.
27
*
28
* A SafeUrl is a string-like object that carries the security type contract
29
* that its value as a string will not cause untrusted script execution
30
* when evaluated as a hyperlink URL in a browser.
31
*
32
* Values of this type are guaranteed to be safe to use in URL/hyperlink
33
* contexts, such as assignment to URL-valued DOM properties, in the sense that
34
* the use will not result in a Cross-Site-Scripting vulnerability. Similarly,
35
* SafeUrls can be interpolated into the URL context of an HTML template (e.g.,
36
* inside a href attribute). However, appropriate HTML-escaping must still be
37
* applied.
38
*
39
* Note that, as documented in `goog.html.SafeUrl.unwrap`, this type's
40
* contract does not guarantee that instances are safe to interpolate into HTML
41
* without appropriate escaping.
42
*
43
* Note also that this type's contract does not imply any guarantees regarding
44
* the resource the URL refers to. In particular, SafeUrls are <b>not</b>
45
* safe to use in a context where the referred-to resource is interpreted as
46
* trusted code, e.g., as the src of a script tag.
47
*
48
* Instances of this type must be created via the factory methods
49
* (`goog.html.SafeUrl.fromConstant`, `goog.html.SafeUrl.sanitize`),
50
* etc and not by invoking its constructor. The constructor intentionally takes
51
* an extra parameter that cannot be constructed outside of this file and the
52
* type is immutable; hence only a default instance corresponding to the empty
53
* string can be obtained via constructor invocation.
54
*
55
* @see goog.html.SafeUrl#fromConstant
56
* @see goog.html.SafeUrl#from
57
* @see goog.html.SafeUrl#sanitize
58
* @final
59
* @struct
60
* @implements {goog.string.TypedString}
61
*/
62
goog.html.SafeUrl = class {
63
/**
64
* @param {string} value
65
* @param {!Object} token package-internal implementation detail.
66
*/
67
constructor(value, token) {
68
if (goog.DEBUG && token !== goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_) {
69
throw Error('SafeUrl is not meant to be built directly');
70
}
71
72
/**
73
* The contained value of this SafeUrl. The field has a purposely ugly
74
* name to make (non-compiled) code that attempts to directly access this
75
* field stand out.
76
* @const
77
* @private {string}
78
*/
79
this.privateDoNotAccessOrElseSafeUrlWrappedValue_ = value;
80
}
81
82
/**
83
* Returns a string-representation of this value.
84
*
85
* To obtain the actual string value wrapped in a SafeUrl, use
86
* `goog.html.SafeUrl.unwrap`.
87
*
88
* @return {string}
89
* @see goog.html.SafeUrl#unwrap
90
* @override
91
*/
92
toString() {
93
return this.privateDoNotAccessOrElseSafeUrlWrappedValue_.toString();
94
}
95
};
96
97
98
/**
99
* The innocuous string generated by goog.html.SafeUrl.sanitize when passed
100
* an unsafe URL.
101
*
102
* about:invalid is registered in
103
* http://www.w3.org/TR/css3-values/#about-invalid.
104
* http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
105
* contain a fragment, which is not to be considered when determining if an
106
* about URL is well-known.
107
*
108
* Using about:invalid seems preferable to using a fixed data URL, since
109
* browsers might choose to not report CSP violations on it, as legitimate
110
* CSS function calls to attr() can result in this URL being produced. It is
111
* also a standard URL which matches exactly the semantics we need:
112
* "The about:invalid URI references a non-existent document with a generic
113
* error condition. It can be used when a URI is necessary, but the default
114
* value shouldn't be resolveable as any type of document".
115
*
116
* @const {string}
117
*/
118
goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
119
120
121
/**
122
* @override
123
* @const
124
*/
125
goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
126
127
128
/**
129
* Returns this SafeUrl's value as a string.
130
*
131
* IMPORTANT: In code where it is security relevant that an object's type is
132
* indeed `SafeUrl`, use `goog.html.SafeUrl.unwrap` instead of this
133
* method. If in doubt, assume that it's security relevant. In particular, note
134
* that goog.html functions which return a goog.html type do not guarantee that
135
* the returned instance is of the right type.
136
*
137
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
138
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
139
* be appropriately escaped before embedding in a HTML document. Note that the
140
* required escaping is context-sensitive (e.g. a different escaping is
141
* required for embedding a URL in a style property within a style
142
* attribute, as opposed to embedding in a href attribute).
143
*
144
* @see goog.html.SafeUrl#unwrap
145
* @override
146
*/
147
goog.html.SafeUrl.prototype.getTypedStringValue = function() {
148
'use strict';
149
return this.privateDoNotAccessOrElseSafeUrlWrappedValue_.toString();
150
};
151
152
/**
153
* Performs a runtime check that the provided object is indeed a SafeUrl
154
* object, and returns its value.
155
*
156
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
157
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
158
* be appropriately escaped before embedding in a HTML document. Note that the
159
* required escaping is context-sensitive (e.g. a different escaping is
160
* required for embedding a URL in a style property within a style
161
* attribute, as opposed to embedding in a href attribute).
162
*
163
* @param {!goog.html.SafeUrl} safeUrl The object to extract from.
164
* @return {string} The SafeUrl object's contained string, unless the run-time
165
* type check fails. In that case, `unwrap` returns an innocuous
166
* string, or, if assertions are enabled, throws
167
* `goog.asserts.AssertionError`.
168
*/
169
goog.html.SafeUrl.unwrap = function(safeUrl) {
170
'use strict';
171
// Perform additional Run-time type-checking to ensure that safeUrl is indeed
172
// an instance of the expected type. This provides some additional protection
173
// against security bugs due to application code that disables type checks.
174
// Specifically, the following checks are performed:
175
// 1. The object is an instance of the expected type.
176
// 2. The object is not an instance of a subclass.
177
if (safeUrl instanceof goog.html.SafeUrl &&
178
safeUrl.constructor === goog.html.SafeUrl) {
179
return safeUrl.privateDoNotAccessOrElseSafeUrlWrappedValue_;
180
} else {
181
goog.asserts.fail(
182
'expected object of type SafeUrl, got \'' + safeUrl + '\' of type ' +
183
goog.utils.typeOf(safeUrl));
184
return 'type_error:SafeUrl';
185
}
186
};
187
188
189
/**
190
* Creates a SafeUrl object from a compile-time constant string.
191
*
192
* Compile-time constant strings are inherently program-controlled and hence
193
* trusted.
194
*
195
* @param {!goog.string.Const} url A compile-time-constant string from which to
196
* create a SafeUrl.
197
* @return {!goog.html.SafeUrl} A SafeUrl object initialized to `url`.
198
*/
199
goog.html.SafeUrl.fromConstant = function(url) {
200
'use strict';
201
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
202
goog.string.Const.unwrap(url));
203
};
204
205
206
/**
207
* A pattern that matches Blob or data types that can have SafeUrls created
208
* from URL.createObjectURL(blob) or via a data: URI.
209
*
210
* This has some parameter support (most notably, we haven't implemented the
211
* more complex parts like %-encoded characters or non-alphanumerical ones for
212
* simplicity's sake). The specs are fairly complex, and they don't
213
* always match Chrome's behavior: we settled on a subset where we're confident
214
* all parties involved agree.
215
*
216
* The spec is available at https://mimesniff.spec.whatwg.org/ (and see
217
* https://tools.ietf.org/html/rfc2397 for data: urls, which override some of
218
* it).
219
* @const
220
* @private
221
*/
222
goog.html.SAFE_MIME_TYPE_PATTERN_ = new RegExp(
223
// Note: Due to content-sniffing concerns, only add MIME types for
224
// media formats.
225
'^(?:audio/(?:3gpp2|3gpp|aac|L16|midi|mp3|mp4|mpeg|oga|ogg|opus|x-m4a|x-matroska|x-wav|wav|webm)|' +
226
'font/\\w+|' +
227
'image/(?:bmp|gif|jpeg|jpg|png|tiff|webp|x-icon|heic|heif)|' +
228
'video/(?:mpeg|mp4|ogg|webm|quicktime|x-matroska))' +
229
'(?:;\\w+=(?:\\w+|"[\\w;,= ]+"))*$', // MIME type parameters
230
'i');
231
232
233
/**
234
* @param {string} mimeType The MIME type to check if safe.
235
* @return {boolean} True if the MIME type is safe and creating a Blob via
236
* `SafeUrl.fromBlob()` with that type will not fail due to the type. False
237
* otherwise.
238
*/
239
goog.html.SafeUrl.isSafeMimeType = function(mimeType) {
240
'use strict';
241
return goog.html.SAFE_MIME_TYPE_PATTERN_.test(mimeType);
242
};
243
244
245
/**
246
* Creates a SafeUrl wrapping a blob URL for the given `blob`.
247
*
248
* The blob URL is created with `URL.createObjectURL`. If the MIME type
249
* for `blob` is not of a known safe audio, image or video MIME type,
250
* then the SafeUrl will wrap {@link #INNOCUOUS_STRING}.
251
*
252
* Note: Call {@link revokeObjectUrl} on the URL after it's used
253
* to prevent memory leaks.
254
*
255
* @see http://www.w3.org/TR/FileAPI/#url
256
* @param {!Blob} blob
257
* @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
258
* as a SafeUrl.
259
*/
260
goog.html.SafeUrl.fromBlob = function(blob) {
261
'use strict';
262
var url = goog.html.SafeUrl.isSafeMimeType(blob.type) ?
263
goog.fs.url.createObjectUrl(blob) :
264
goog.html.SafeUrl.INNOCUOUS_STRING;
265
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
266
};
267
268
269
/**
270
* Revokes an object URL created for a safe URL created {@link fromBlob()}.
271
* @param {!goog.html.SafeUrl} safeUrl SafeUrl wrapping a blob object.
272
* @return {void}
273
*/
274
goog.html.SafeUrl.revokeObjectUrl = function(safeUrl) {
275
'use strict';
276
var url = safeUrl.getTypedStringValue();
277
if (url !== goog.html.SafeUrl.INNOCUOUS_STRING) {
278
goog.fs.url.revokeObjectUrl(url);
279
}
280
};
281
282
283
/**
284
* Creates a SafeUrl wrapping a blob URL created for a MediaSource.
285
* @param {!MediaSource} mediaSource
286
* @return {!goog.html.SafeUrl} The blob URL.
287
*/
288
goog.html.SafeUrl.fromMediaSource = function(mediaSource) {
289
'use strict';
290
goog.asserts.assert(
291
'MediaSource' in goog.global, 'No support for MediaSource');
292
const url = mediaSource instanceof MediaSource ?
293
goog.fs.url.createObjectUrl(mediaSource) :
294
goog.html.SafeUrl.INNOCUOUS_STRING;
295
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
296
};
297
298
299
/**
300
* Matches a base-64 data URL, with the first match group being the MIME type.
301
* @const
302
* @private
303
*/
304
goog.html.DATA_URL_PATTERN_ = /^data:(.*);base64,[a-z0-9+\/]+=*$/i;
305
306
307
/**
308
* Attempts to create a SafeUrl wrapping a `data:` URL, after validating it
309
* matches a known-safe media MIME type. If it doesn't match, return `null`.
310
*
311
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
312
* media MIME types.
313
* @return {?goog.html.SafeUrl} A matching safe URL, or `null` if it does not
314
* pass.
315
*/
316
goog.html.SafeUrl.tryFromDataUrl = function(dataUrl) {
317
'use strict';
318
// For defensive purposes, in case users cast around the parameter type.
319
dataUrl = String(dataUrl);
320
// RFC4648 suggest to ignore CRLF in base64 encoding.
321
// See https://tools.ietf.org/html/rfc4648.
322
// Remove the CR (%0D) and LF (%0A) from the dataUrl.
323
var filteredDataUrl = dataUrl.replace(/(%0A|%0D)/g, '');
324
var match = filteredDataUrl.match(goog.html.DATA_URL_PATTERN_);
325
// Note: The only risk of XSS here is if the `data:` URL results in a
326
// same-origin document. In which case content-sniffing might cause the
327
// browser to interpret the contents as html.
328
// All modern browsers consider `data:` URL documents to have unique empty
329
// origins. Only Firefox for versions prior to v57 behaves differently:
330
// https://blog.mozilla.org/security/2017/10/04/treating-data-urls-unique-origins-firefox-57/
331
// Older versions of IE don't understand `data:` urls, so it is not an issue.
332
if (match) {
333
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
334
filteredDataUrl);
335
}
336
return null;
337
};
338
339
340
/**
341
* Creates a SafeUrl wrapping a `data:` URL, after validating it matches a
342
* known-safe media MIME type. If it doesn't match, return
343
* `goog.html.SafeUrl.INNOCUOUS_URL`.
344
*
345
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
346
* media MIME types.
347
* @return {!goog.html.SafeUrl} A matching safe URL, or
348
* `goog.html.SafeUrl.INNOCUOUS_URL` if it does not pass.
349
*/
350
goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
351
'use strict';
352
return goog.html.SafeUrl.tryFromDataUrl(dataUrl) ||
353
goog.html.SafeUrl.INNOCUOUS_URL;
354
};
355
356
357
/**
358
* Creates a SafeUrl wrapping a tel: URL.
359
*
360
* @param {string} telUrl A tel URL.
361
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
362
* wrapped as a SafeUrl if it does not pass.
363
*/
364
goog.html.SafeUrl.fromTelUrl = function(telUrl) {
365
'use strict';
366
// There's a risk that a tel: URL could immediately place a call once
367
// clicked, without requiring user confirmation. For that reason it is
368
// handled in this separate function.
369
if (!goog.string.internal.caseInsensitiveStartsWith(telUrl, 'tel:')) {
370
telUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
371
}
372
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
373
telUrl);
374
};
375
376
377
/**
378
* Matches a sip/sips URL. We only allow urls that consist of an email address.
379
* The characters '?' and '#' are not allowed in the local part of the email
380
* address.
381
* @const
382
* @private
383
*/
384
goog.html.SIP_URL_PATTERN_ = new RegExp(
385
'^sip[s]?:[+a-z0-9_.!$%&\'*\\/=^`{|}~-]+@([a-z0-9-]+\\.)+[a-z0-9]{2,63}$',
386
'i');
387
388
389
/**
390
* Creates a SafeUrl wrapping a sip: URL. We only allow urls that consist of an
391
* email address. The characters '?' and '#' are not allowed in the local part
392
* of the email address.
393
*
394
* @param {string} sipUrl A sip URL.
395
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
396
* wrapped as a SafeUrl if it does not pass.
397
*/
398
goog.html.SafeUrl.fromSipUrl = function(sipUrl) {
399
'use strict';
400
if (!goog.html.SIP_URL_PATTERN_.test(decodeURIComponent(sipUrl))) {
401
sipUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
402
}
403
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
404
sipUrl);
405
};
406
407
408
/**
409
* Creates a SafeUrl wrapping a fb-messenger://share URL.
410
*
411
* @param {string} facebookMessengerUrl A facebook messenger URL.
412
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
413
* wrapped as a SafeUrl if it does not pass.
414
*/
415
goog.html.SafeUrl.fromFacebookMessengerUrl = function(facebookMessengerUrl) {
416
'use strict';
417
if (!goog.string.internal.caseInsensitiveStartsWith(
418
facebookMessengerUrl, 'fb-messenger://share')) {
419
facebookMessengerUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
420
}
421
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
422
facebookMessengerUrl);
423
};
424
425
/**
426
* Creates a SafeUrl wrapping a whatsapp://send URL.
427
*
428
* @param {string} whatsAppUrl A WhatsApp URL.
429
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
430
* wrapped as a SafeUrl if it does not pass.
431
*/
432
goog.html.SafeUrl.fromWhatsAppUrl = function(whatsAppUrl) {
433
'use strict';
434
if (!goog.string.internal.caseInsensitiveStartsWith(
435
whatsAppUrl, 'whatsapp://send')) {
436
whatsAppUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
437
}
438
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
439
whatsAppUrl);
440
};
441
442
/**
443
* Creates a SafeUrl wrapping a sms: URL.
444
*
445
* @param {string} smsUrl A sms URL.
446
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
447
* wrapped as a SafeUrl if it does not pass.
448
*/
449
goog.html.SafeUrl.fromSmsUrl = function(smsUrl) {
450
'use strict';
451
if (!goog.string.internal.caseInsensitiveStartsWith(smsUrl, 'sms:') ||
452
!goog.html.SafeUrl.isSmsUrlBodyValid_(smsUrl)) {
453
smsUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
454
}
455
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
456
smsUrl);
457
};
458
459
460
/**
461
* Validates SMS URL `body` parameter, which is optional and should appear at
462
* most once and should be percent-encoded if present. Rejects many malformed
463
* bodies, but may spuriously reject some URLs and does not reject all malformed
464
* sms: URLs.
465
*
466
* @param {string} smsUrl A sms URL.
467
* @return {boolean} Whether SMS URL has a valid `body` parameter if it exists.
468
* @private
469
*/
470
goog.html.SafeUrl.isSmsUrlBodyValid_ = function(smsUrl) {
471
'use strict';
472
var hash = smsUrl.indexOf('#');
473
if (hash > 0) {
474
smsUrl = smsUrl.substring(0, hash);
475
}
476
var bodyParams = smsUrl.match(/[?&]body=/gi);
477
// "body" param is optional
478
if (!bodyParams) {
479
return true;
480
}
481
// "body" MUST only appear once
482
if (bodyParams.length > 1) {
483
return false;
484
}
485
// Get the encoded `body` parameter value.
486
var bodyValue = smsUrl.match(/[?&]body=([^&]*)/)[1];
487
if (!bodyValue) {
488
return true;
489
}
490
try {
491
decodeURIComponent(bodyValue);
492
} catch (error) {
493
return false;
494
}
495
return /^(?:[a-z0-9\-_.~]|%[0-9a-f]{2})+$/i.test(bodyValue);
496
};
497
498
499
/**
500
* Creates a SafeUrl wrapping a ssh: URL.
501
*
502
* @param {string} sshUrl A ssh URL.
503
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
504
* wrapped as a SafeUrl if it does not pass.
505
*/
506
goog.html.SafeUrl.fromSshUrl = function(sshUrl) {
507
'use strict';
508
if (!goog.string.internal.caseInsensitiveStartsWith(sshUrl, 'ssh://')) {
509
sshUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
510
}
511
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
512
sshUrl);
513
};
514
515
/**
516
* Sanitizes a Chrome extension URL to SafeUrl, given a compile-time-constant
517
* extension identifier. Can also be restricted to chrome extensions.
518
*
519
* @param {string} url The url to sanitize. Should start with the extension
520
* scheme and the extension identifier.
521
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
522
* extension id to accept, as a compile-time constant or an array of those.
523
*
524
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
525
* `INNOCUOUS_STRING` if it's not.
526
*/
527
goog.html.SafeUrl.sanitizeChromeExtensionUrl = function(url, extensionId) {
528
'use strict';
529
return goog.html.SafeUrl.sanitizeExtensionUrl_(
530
/^chrome-extension:\/\/([^\/]+)\//, url, extensionId);
531
};
532
533
/**
534
* Sanitizes a Firefox extension URL to SafeUrl, given a compile-time-constant
535
* extension identifier. Can also be restricted to chrome extensions.
536
*
537
* @param {string} url The url to sanitize. Should start with the extension
538
* scheme and the extension identifier.
539
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
540
* extension id to accept, as a compile-time constant or an array of those.
541
*
542
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
543
* `INNOCUOUS_STRING` if it's not.
544
*/
545
goog.html.SafeUrl.sanitizeFirefoxExtensionUrl = function(url, extensionId) {
546
'use strict';
547
return goog.html.SafeUrl.sanitizeExtensionUrl_(
548
/^moz-extension:\/\/([^\/]+)\//, url, extensionId);
549
};
550
551
/**
552
* Sanitizes a Edge extension URL to SafeUrl, given a compile-time-constant
553
* extension identifier. Can also be restricted to chrome extensions.
554
*
555
* @param {string} url The url to sanitize. Should start with the extension
556
* scheme and the extension identifier.
557
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
558
* extension id to accept, as a compile-time constant or an array of those.
559
*
560
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
561
* `INNOCUOUS_STRING` if it's not.
562
*/
563
goog.html.SafeUrl.sanitizeEdgeExtensionUrl = function(url, extensionId) {
564
'use strict';
565
return goog.html.SafeUrl.sanitizeExtensionUrl_(
566
/^ms-browser-extension:\/\/([^\/]+)\//, url, extensionId);
567
};
568
569
/**
570
* Private helper for converting extension URLs to SafeUrl, given the scheme for
571
* that particular extension type. Use the sanitizeFirefoxExtensionUrl,
572
* sanitizeChromeExtensionUrl or sanitizeEdgeExtensionUrl unless you're building
573
* new helpers.
574
*
575
* @private
576
* @param {!RegExp} scheme The scheme to accept as a RegExp extracting the
577
* extension identifier.
578
* @param {string} url The url to sanitize. Should start with the extension
579
* scheme and the extension identifier.
580
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
581
* extension id to accept, as a compile-time constant or an array of those.
582
*
583
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
584
* `INNOCUOUS_STRING` if it's not.
585
*/
586
goog.html.SafeUrl.sanitizeExtensionUrl_ = function(scheme, url, extensionId) {
587
'use strict';
588
var matches = scheme.exec(url);
589
if (!matches) {
590
url = goog.html.SafeUrl.INNOCUOUS_STRING;
591
} else {
592
var extractedExtensionId = matches[1];
593
var acceptedExtensionIds;
594
if (extensionId instanceof goog.string.Const) {
595
acceptedExtensionIds = [goog.string.Const.unwrap(extensionId)];
596
} else {
597
acceptedExtensionIds = extensionId.map(function unwrap(x) {
598
'use strict';
599
return goog.string.Const.unwrap(x);
600
});
601
}
602
if (acceptedExtensionIds.indexOf(extractedExtensionId) == -1) {
603
url = goog.html.SafeUrl.INNOCUOUS_STRING;
604
}
605
}
606
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
607
};
608
609
610
/**
611
* Creates a SafeUrl from TrustedResourceUrl. This is safe because
612
* TrustedResourceUrl is more tightly restricted than SafeUrl.
613
*
614
* @param {!goog.html.TrustedResourceUrl} trustedResourceUrl
615
* @return {!goog.html.SafeUrl}
616
*/
617
goog.html.SafeUrl.fromTrustedResourceUrl = function(trustedResourceUrl) {
618
'use strict';
619
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
620
goog.html.TrustedResourceUrl.unwrap(trustedResourceUrl));
621
};
622
623
624
/**
625
* A pattern that recognizes a commonly useful subset of URLs that satisfy
626
* the SafeUrl contract.
627
*
628
* This regular expression matches a subset of URLs that will not cause script
629
* execution if used in URL context within a HTML document. Specifically, this
630
* regular expression matches if (comment from here on and regex copied from
631
* Soy's EscapingConventions):
632
* (1) Either a protocol in a whitelist (http, https, mailto or ftp).
633
* (2) or no protocol. A protocol must be followed by a colon. The below
634
* allows that by allowing colons only after one of the characters [/?#].
635
* A colon after a hash (#) must be in the fragment.
636
* Otherwise, a colon after a (?) must be in a query.
637
* Otherwise, a colon after a single solidus (/) must be in a path.
638
* Otherwise, a colon after a double solidus (//) must be in the authority
639
* (before port).
640
*
641
* @private
642
* @const {!RegExp}
643
*/
644
goog.html.SAFE_URL_PATTERN_ =
645
/^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i;
646
647
/**
648
* Public version of goog.html.SAFE_URL_PATTERN_. Updating
649
* goog.html.SAFE_URL_PATTERN_ doesn't seem to be backward compatible.
650
* Namespace is also changed to goog.html.SafeUrl so it can be imported using
651
* goog.require('goog.dom.SafeUrl').
652
*
653
* TODO(bangert): Remove SAFE_URL_PATTERN_
654
* @const {!RegExp}
655
*/
656
goog.html.SafeUrl.SAFE_URL_PATTERN = goog.html.SAFE_URL_PATTERN_;
657
658
/**
659
* Attempts to create a SafeUrl object from `url`. The input string is validated
660
* to match a pattern of commonly used safe URLs. If validation fails, `null` is
661
* returned.
662
*
663
* `url` may be a URL with the `http:`, `https:`, `mailto:`, `ftp:` or `data`
664
* scheme, or a relative URL (i.e., a URL without a scheme; specifically, a
665
* scheme-relative, absolute-path-relative, or path-relative URL).
666
*
667
* @see http://url.spec.whatwg.org/#concept-relative-url
668
* @param {string|!goog.string.TypedString} url The URL to validate.
669
* @return {?goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl, or null
670
* if validation fails.
671
*/
672
goog.html.SafeUrl.trySanitize = function(url) {
673
'use strict';
674
if (url instanceof goog.html.SafeUrl) {
675
return url;
676
}
677
if (typeof url == 'object' && url.implementsGoogStringTypedString) {
678
url = /** @type {!goog.string.TypedString} */ (url).getTypedStringValue();
679
} else {
680
// For defensive purposes, in case users cast around the parameter type.
681
url = String(url);
682
}
683
if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
684
return goog.html.SafeUrl.tryFromDataUrl(url);
685
}
686
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
687
};
688
689
/**
690
* Creates a SafeUrl object from `url`. If `url` is a
691
* `goog.html.SafeUrl` then it is simply returned. Otherwise the input string is
692
* validated to match a pattern of commonly used safe URLs. If validation fails,
693
* `goog.html.SafeUrl.INNOCUOUS_URL` is returned.
694
*
695
* `url` may be a URL with the `http:`, `https:`, `mailto:`, `ftp:` or `data`
696
* scheme, or a relative URL (i.e., a URL without a scheme; specifically, a
697
* scheme-relative, absolute-path-relative, or path-relative URL).
698
*
699
* @see http://url.spec.whatwg.org/#concept-relative-url
700
* @param {string|!goog.string.TypedString} url The URL to validate.
701
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
702
*/
703
goog.html.SafeUrl.sanitize = function(url) {
704
'use strict';
705
return goog.html.SafeUrl.trySanitize(url) || goog.html.SafeUrl.INNOCUOUS_URL;
706
};
707
708
/**
709
* Creates a SafeUrl object from `url`. If `url` is a
710
* `goog.html.SafeUrl` then it is simply returned. Otherwise the input string is
711
* validated to match a pattern of commonly used safe URLs.
712
*
713
* `url` may be a URL with the http, https, mailto or ftp scheme,
714
* or a relative URL (i.e., a URL without a scheme; specifically, a
715
* scheme-relative, absolute-path-relative, or path-relative URL).
716
*
717
* This function asserts (using goog.asserts) that the URL matches this pattern.
718
* If it does not, in addition to failing the assert, an innocuous URL will be
719
* returned.
720
*
721
* @see http://url.spec.whatwg.org/#concept-relative-url
722
* @param {string|!goog.string.TypedString} url The URL to validate.
723
* @param {boolean=} opt_allowDataUrl Whether to allow valid data: URLs.
724
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
725
*/
726
goog.html.SafeUrl.sanitizeAssertUnchanged = function(url, opt_allowDataUrl) {
727
'use strict';
728
if (url instanceof goog.html.SafeUrl) {
729
return url;
730
} else if (typeof url == 'object' && url.implementsGoogStringTypedString) {
731
url = /** @type {!goog.string.TypedString} */ (url).getTypedStringValue();
732
} else {
733
url = String(url);
734
}
735
if (opt_allowDataUrl && /^data:/i.test(url)) {
736
var safeUrl = goog.html.SafeUrl.fromDataUrl(url);
737
if (safeUrl.getTypedStringValue() == url) {
738
return safeUrl;
739
}
740
}
741
if (!goog.asserts.assert(
742
goog.html.SAFE_URL_PATTERN_.test(url),
743
'%s does not match the safe URL pattern', url)) {
744
url = goog.html.SafeUrl.INNOCUOUS_STRING;
745
}
746
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
747
};
748
749
/**
750
* Extracts the scheme from the given URL. If the URL is relative, https: is
751
* assumed.
752
* @param {string} url The URL to extract the scheme from.
753
* @return {string|undefined} the URL scheme.
754
*/
755
goog.html.SafeUrl.extractScheme = function(url) {
756
let parsedUrl;
757
try {
758
parsedUrl = new URL(url);
759
} catch (e) {
760
// According to https://url.spec.whatwg.org/#constructors, the URL
761
// constructor with one parameter throws if `url` is not absolute. In this
762
// case, we are sure that no explicit scheme (javascript: ) is set.
763
// This can also be a URL parsing error, but in this case the URL won't be
764
// run anyway.
765
return 'https:';
766
}
767
return parsedUrl.protocol;
768
};
769
770
/**
771
* Creates a SafeUrl object from `url`. If `url` is a
772
* `goog.html.SafeUrl` then it is simply returned. Otherwise javascript: URLs
773
* are rejected.
774
*
775
* This function asserts (using goog.asserts) that the URL scheme is not
776
* javascript. If it is, in addition to failing the assert, an innocuous URL
777
* will be returned.
778
*
779
* @see http://url.spec.whatwg.org/#concept-relative-url
780
* @param {string|!goog.string.TypedString} url The URL to validate.
781
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
782
*/
783
goog.html.SafeUrl.sanitizeJavascriptUrlAssertUnchanged = function(url) {
784
'use strict';
785
if (url instanceof goog.html.SafeUrl) {
786
return url;
787
} else if (typeof url == 'object' && url.implementsGoogStringTypedString) {
788
url = /** @type {!goog.string.TypedString} */ (url).getTypedStringValue();
789
} else {
790
url = String(url);
791
}
792
// We don't rely on goog.url here to prevent a dependency cycle.
793
const parsedScheme = goog.html.SafeUrl.extractScheme(url);
794
if (!goog.asserts.assert(
795
parsedScheme !== 'javascript:', '%s is a javascript: URL', url)) {
796
url = goog.html.SafeUrl.INNOCUOUS_STRING;
797
}
798
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
799
};
800
801
/**
802
* Token used to ensure that object is created only from this file. No code
803
* outside of this file can access this token.
804
* @private {!Object}
805
* @const
806
*/
807
goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_ = {};
808
809
/**
810
* Package-internal utility method to create SafeUrl instances.
811
*
812
* @param {string} url The string to initialize the SafeUrl object with.
813
* @return {!goog.html.SafeUrl} The initialized SafeUrl object.
814
* @package
815
*/
816
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
817
url) {
818
'use strict';
819
return new goog.html.SafeUrl(
820
url, goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_);
821
};
822
823
824
/**
825
* `INNOCUOUS_STRING` wrapped in a `SafeUrl`.
826
* @const {!goog.html.SafeUrl}
827
*/
828
goog.html.SafeUrl.INNOCUOUS_URL =
829
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
830
goog.html.SafeUrl.INNOCUOUS_STRING);
831
832
833
/**
834
* A SafeUrl corresponding to the special about:blank url.
835
* @const {!goog.html.SafeUrl}
836
*/
837
goog.html.SafeUrl.ABOUT_BLANK =
838
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
839
'about:blank');
840
841