Skip to content
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

Closed
wants to merge 5 commits into from

Conversation

mhevery
Copy link
Contributor

@mhevery mhevery commented Mar 1, 2018

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?

[ ] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] angular.io application / infrastructure changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

[ ] Yes
[ ] No

Other information

@alexeagle alexeagle added the area: core Issues related to the framework runtime label Mar 2, 2018
/**
* Function used to sanitize the value before writing it into the renderer.
*/
export type SanitizerFn = (value: any) => string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stringifyer ? (more generic)

Copy link
Contributor Author

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.

Copy link
Contributor

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update API docs

Copy link
Contributor Author

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;
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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 ?

Copy link
Contributor Author

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;
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

Copy link
Contributor

@vicb vicb Mar 2, 2018

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()"'

Copy link
Contributor Author

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.

Copy link
Contributor

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;
Copy link
Contributor

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 }

Copy link
Contributor Author

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('="');
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returns el is HTMLTemplateElement ?

Copy link
Contributor Author

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
Copy link
Contributor

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)

Copy link
Contributor Author

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 {
Copy link
Contributor

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())

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto string

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console && console.warn ?

Copy link
Contributor Author

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.

Copy link
Contributor

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)`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it

Copy link
Contributor Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shorter name for ɵsanitizers ?

Copy link
Contributor Author

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.

@vicb
Copy link
Contributor

vicb commented Mar 2, 2018

Second commit is not really/only: "feat(ivy): add canonical spec for sanitization" ?

@mhevery
Copy link
Contributor Author

mhevery commented Mar 2, 2018

@pkozlowski-opensource This is only sort of breaking change since Render2 still goes through injection. Only Ivy with Render3 would have it fixed. Configuration and tree shaking are the opposing forces which we are trying to balance.

Copy link
Contributor Author

@mhevery mhevery left a 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;
Copy link
Contributor Author

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(
Copy link
Contributor Author

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;
Copy link
Contributor Author

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;
Copy link
Contributor Author

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;
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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(
Copy link
Contributor Author

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)`);
Copy link
Contributor Author

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', () => {
Copy link
Contributor Author

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);
Copy link
Contributor Author

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.

@mhevery mhevery force-pushed the spec_sanitize branch 3 times, most recently from 40b598f to 6cc895d Compare March 2, 2018 21:16
* Otherwise the attribute value is set to the stringified value.
* @param sanitizerFn An optional function which is responsible for sanitizing the `value`.
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.
Copy link
Contributor

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 ?

Copy link
Contributor Author

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes oops

@mhevery mhevery changed the title refactor(core): move sanitization into common directory refactor(core): move sanitization into core directory Mar 2, 2018
* @param updateBlock Optional instructions which go after the creation block:
* `if (creationMode) { ... } __here__`.
*/
constructor(private createBlock: () => void, updateBlock?: () => void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting is off here

Copy link
Contributor Author

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() {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default value ?

Copy link
Contributor Author

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private ?

Copy link
Contributor Author

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 {
Copy link
Contributor

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

Copy link
Contributor Author

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.

@mhevery
Copy link
Contributor Author

mhevery commented Mar 3, 2018

@mhevery mhevery added the target: major This PR is targeted for the next major release label Mar 3, 2018
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));
Copy link
Contributor

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 ?

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string ?

Copy link
Contributor Author

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

@mhevery mhevery added the action: merge The PR is ready for merge by the caretaker label Mar 6, 2018
@kara kara closed this in 538f1d9 Mar 8, 2018
kara pushed a commit that referenced this pull request Mar 8, 2018
)

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
leo6104 pushed a commit to leo6104/angular that referenced this pull request Mar 25, 2018
This is in preparation of having Ivy have sanitization inline.

PR Close angular#22540
leo6104 pushed a commit to leo6104/angular that referenced this pull request Mar 25, 2018
…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
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants