New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(core): move sanitization into core directory #22540
Conversation
/** | ||
* Function used to sanitize the value before writing it into the renderer. | ||
*/ | ||
export type SanitizerFn = (value: any) => string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stringifyer
? (more generic)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it is specifically use for sanitization. I feel like strigifier
is too generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question is: should "But it is specifically use for sanitization" be "I specifically use for sanitization" ?
@@ -687,16 +692,17 @@ export function elementEnd() { | |||
* @param any value The attribute is removed when value is `null` or `undefined`. | |||
* Otherwise the attribute value is set to the stringified value. | |||
*/ | |||
export function elementAttribute(index: number, name: string, value: any): void { | |||
export function elementAttribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update API docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -725,6 +732,9 @@ export function elementProperty<T>(index: number, propName: string, value: T | N | |||
tNode.inputs = generatePropertyAliases(node.flags, BindingDirection.Input); | |||
} | |||
|
|||
if (sanitizerFn != null) { | |||
value = sanitizerFn(stringify(value)) as any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type SanitizerFn = (value: any) => string;
-> do not stringify
? elementAttribute
doesn't above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, since properties get passed in without strigifing, such as when parent component is passing data to child component. We add the sanitization only when compiler detects img
and property src
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure to understand why you stringify(value)
then ?
Could you add a comment in the code ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry maybe I don't understand your question. Can you ask you question in more detail?
if (value !== NO_CHANGE) { | ||
const lElement = data[index] as LElementNode; | ||
if (value == null) { | ||
isProceduralRenderer(renderer) ? | ||
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) : | ||
lElement.native.style.removeProperty(styleName); | ||
} else { | ||
const strValue = suffix ? stringify(value) + suffix : stringify(value); | ||
let strValue = sanitizerFn == null ? stringify(value) : sanitizerFn(value); | ||
if (suffix) strValue = strValue + suffix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the (stringify(value) + prefix) be sanitized instead ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can't be. Since by appending suffix it becomes string and by that point bypass tokens would be stringified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can be or it should be ?
suffix='px" script="evil()"'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we control the suffix, so we know that suffix is safe. You can't get into situation where adding suffix would make the content unsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this suppose to become a public API ?
for (let i = 0; i < elAttrs.length; i++) { | ||
const elAttr = elAttrs.item(i); | ||
const attrName = elAttr.name; | ||
let value = elAttr.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move value after the if { continue }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -156,12 +157,12 @@ class SanitizingHtmlSerializer { | |||
this.buf.push('="'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while updating this code push(a, b, c)
would save some bytes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
} | ||
} | ||
} | ||
|
||
function getTemplateContent(el: Node): Node|null { | ||
return 'content' in el && isTemplateElement(el) ? (<any>el).content : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(<any>el).content
could be el.content
if isTemplateElement
returns el is HTMLTemplateElement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
function getTemplateContent(el: Node): Node|null { | ||
return 'content' in el && isTemplateElement(el) ? (<any>el).content : null; | ||
} | ||
function isTemplateElement(el: Node): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returns el is HTMLTemplateElement
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
} | ||
|
||
/** | ||
* An `html` sanitizer which converts untrusted `html` string into trusted string by removing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which converts untrusted 'html' **string**
not a string (any)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* @returns `html` string which is safe to display to user, because all of the dangerous javascript | ||
* and urls have been removed. | ||
*/ | ||
export function htmlSanitizer(unsafeHtml: any): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be called sanitizeHtml()
(the other could be html.sanitize()
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed for all methods
} | ||
|
||
/** | ||
* A `style` sanitizer which converts untrusted `style` string into trusted string by removing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
} | ||
|
||
/** | ||
* A `url` sanitizer which converts untrusted `url` string into trusted string by removing dangerous |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -98,7 +96,7 @@ export function sanitizeStyle(value: string): string { | |||
} | |||
|
|||
if (isDevMode()) { | |||
getDOM().log( | |||
console.warn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console && console.warn
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why guard? All system should have it. Also if console
is not found that console && ..
will throw you have to say typeof console !== 'undefined' && console.warn
and we don't do it anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one place I have on top of my head https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts#L105 - probably wrong there too...
@@ -51,7 +49,7 @@ export function sanitizeUrl(url: string): string { | |||
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url; | |||
|
|||
if (isDevMode()) { | |||
getDOM().log(`WARNING: sanitizing unsafe URL value ${url} (see http://g.co/ng/security#xss)`); | |||
console.warn(`WARNING: sanitizing unsafe URL value ${url} (see http://g.co/ng/security#xss)`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment
|
||
describe('compiler sanitization', () => { | ||
type $boolean$ = boolean; | ||
fit('should translate DOM structure', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
$r3$.ɵE(2, 'script'); | ||
$r3$.ɵe(); | ||
} | ||
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵhtmlSanitizer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shorter name for ɵsanitizers ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could, they are pretty rare since most properties will not have them.
Second commit is not really/only: "feat(ivy): add canonical spec for sanitization" ? |
@pkozlowski-opensource This is only sort of breaking change since |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL
/** | ||
* Function used to sanitize the value before writing it into the renderer. | ||
*/ | ||
export type SanitizerFn = (value: any) => string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it is specifically use for sanitization. I feel like strigifier
is too generic.
@@ -687,16 +692,17 @@ export function elementEnd() { | |||
* @param any value The attribute is removed when value is `null` or `undefined`. | |||
* Otherwise the attribute value is set to the stringified value. | |||
*/ | |||
export function elementAttribute(index: number, name: string, value: any): void { | |||
export function elementAttribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -725,6 +732,9 @@ export function elementProperty<T>(index: number, propName: string, value: T | N | |||
tNode.inputs = generatePropertyAliases(node.flags, BindingDirection.Input); | |||
} | |||
|
|||
if (sanitizerFn != null) { | |||
value = sanitizerFn(stringify(value)) as any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, since properties get passed in without strigifing, such as when parent component is passing data to child component. We add the sanitization only when compiler detects img
and property src
.
if (value !== NO_CHANGE) { | ||
const lElement = data[index] as LElementNode; | ||
if (value == null) { | ||
isProceduralRenderer(renderer) ? | ||
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) : | ||
lElement.native.style.removeProperty(styleName); | ||
} else { | ||
const strValue = suffix ? stringify(value) + suffix : stringify(value); | ||
let strValue = sanitizerFn == null ? stringify(value) : sanitizerFn(value); | ||
if (suffix) strValue = strValue + suffix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can't be. Since by appending suffix it becomes string and by that point bypass tokens would be stringified.
for (let i = 0; i < elAttrs.length; i++) { | ||
const elAttr = elAttrs.item(i); | ||
const attrName = elAttr.name; | ||
let value = elAttr.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
* @returns `html` string which is safe to display to user, because all of the dangerous javascript | ||
* and urls have been removed. | ||
*/ | ||
export function htmlSanitizer(unsafeHtml: any): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed for all methods
@@ -98,7 +96,7 @@ export function sanitizeStyle(value: string): string { | |||
} | |||
|
|||
if (isDevMode()) { | |||
getDOM().log( | |||
console.warn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why guard? All system should have it. Also if console
is not found that console && ..
will throw you have to say typeof console !== 'undefined' && console.warn
and we don't do it anywhere else.
@@ -51,7 +49,7 @@ export function sanitizeUrl(url: string): string { | |||
if (url.match(SAFE_URL_PATTERN) || url.match(DATA_URL_PATTERN)) return url; | |||
|
|||
if (isDevMode()) { | |||
getDOM().log(`WARNING: sanitizing unsafe URL value ${url} (see http://g.co/ng/security#xss)`); | |||
console.warn(`WARNING: sanitizing unsafe URL value ${url} (see http://g.co/ng/security#xss)`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment
|
||
describe('compiler sanitization', () => { | ||
type $boolean$ = boolean; | ||
fit('should translate DOM structure', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
$r3$.ɵE(2, 'script'); | ||
$r3$.ɵe(); | ||
} | ||
$r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵhtmlSanitizer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could, they are pretty rare since most properties will not have them.
40b598f
to
6cc895d
Compare
* Otherwise the attribute value is set to the stringified value. | ||
* @param sanitizerFn An optional function which is responsible for sanitizing the `value`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ie @param stringifier An optional function used to transform the value typically used for sanitization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed all references
} | ||
} | ||
} | ||
} | ||
|
||
function getTemplateContent(el: Node): Node|null { | ||
return 'content' in el && isTemplateElement(el) ? el.content : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why 'content' in el
is required here (on top of isTemplateElement
)
Add a comment ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea, it is copy paste from DOM adapter. Maybe some browser don't have content
?
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) { | ||
this.DOM.removeAttribute(el, attrName); | ||
el.removeAttribute(attrName); | ||
i--; // compensate for the removal of attribute. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please no. loop backward instead ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, fixed
*/ | ||
export function elementStyle<T>( | ||
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string | null, | ||
sanitizerFn?: SanitizerFn): void { | ||
debugger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes oops
* @param updateBlock Optional instructions which go after the creation block: | ||
* `if (creationMode) { ... } __here__`. | ||
*/ | ||
constructor(private createBlock: () => void, updateBlock?: () => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatting is off here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* `if (creationMode) { ... } __here__`. | ||
*/ | ||
constructor(private createBlock: () => void, updateBlock?: () => void) { | ||
this.updateBlock = updateBlock || function() {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default value ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
|
||
hostNode: LElementNode; | ||
|
||
updateBlock: () => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* - maintaining the template state between invocations, | ||
* - access to the render `html`. | ||
*/ | ||
export class TemplateFixture { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice !
That was the kind of helper I had in mind
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought you would. We can really add lots of features on top of the fixture to make testing easier.
This is in preparation of having Ivy have sanitization inline.
By providing a top level sanitization methods (rather than service) the compiler can generate calls into the methods only when needed. This makes the methods tree shakable.
if (value !== NO_CHANGE) { | ||
const lElement = data[index] as LElementNode; | ||
if (value == null) { | ||
isProceduralRenderer(renderer) ? | ||
renderer.removeStyle(lElement.native, styleName, RendererStyleFlags3.DashCase) : | ||
lElement.native.style.removeProperty(styleName); | ||
} else { | ||
const strValue = suffix ? stringify(value) + suffix : stringify(value); | ||
let strValue = stringifier == null ? stringify(value) : stringifier(value); | ||
if (suffix) strValue = strValue + (stringifier == null ? suffix : stringifier(suffix)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about:
if (stringifier == null) {
v = stringify(value);
} else {
v = suffix ? stringifier(stringify(value) + suffix) : stringifier(value);
}
If suffix
is the unit, this should work fine ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
} | ||
|
||
/** | ||
* An `html` sanitizer which converts untrusted `html` **string** into trusted string by removing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left as is per our discussion
) By providing a top level sanitization methods (rather than service) the compiler can generate calls into the methods only when needed. This makes the methods tree shakable. PR Close #22540
This is in preparation of having Ivy have sanitization inline. PR Close angular#22540
…ular#22540) By providing a top level sanitization methods (rather than service) the compiler can generate calls into the methods only when needed. This makes the methods tree shakable. PR Close angular#22540
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This is in preparation of having Ivy have sanitization inline.
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information