شاهد كل من اضطر إلى تضمين محتوى HTML في DOM في Angular هذه الرسالة. بالطبع ، نحصل جميعًا على المحتوى المحدد من خادمنا ونريد فقط تغطية رسالة الخطأ. أو نقوم بتضمين HTML من ثوابتنا ، مضمنًا أيقونات SVG الخاصة بنا ، لأننا نحتاج فقط إلى تلوينها بلون النص. بعد كل شيء ، لن يحدث أي شيء سيء إذا قلنا لـ Angular - وليس الانجراف ، كل شيء نظيف هناك.
غالبًا ما يكون هذا الأمر صحيحًا ، ولكن في المشاريع الكبيرة التي تضم مجموعة من المطورين الذين يكتبون مكونات مستقلة ، لا تعرف أبدًا مكان تواجد الكود. وإذا كنت ، مثلي ، تقوم بتطوير مكتبة تحتوي على مكونات قابلة لإعادة الاستخدام ، فإن هذا الموقف يحتاج إلى حل في مهده.

تطهير و DomSanitizer
يحتوي Angular على فئات مجردة تم تصميم تطبيقاتها لمسح محتويات غير المرغوب فيه الضارة:
abstract class Sanitizer { abstract sanitize(context: SecurityContext, value: string | {}): string | null }
abstract class DomSanitizer implements Sanitizer { abstract sanitize(context: SecurityContext, value: string | SafeValue): string | null abstract bypassSecurityTrustHtml(value: string): SafeHtml abstract bypassSecurityTrustStyle(value: string): SafeStyle abstract bypassSecurityTrustScript(value: string): SafeScript abstract bypassSecurityTrustUrl(value: string): SafeUrl abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl }
الطبقة الداخلية المحددة DomSanitizerImpl
Angular تستخدم في بناء DOM. إن أفراده هم الذين يضيفون إلى التوجيهات والمكونات والأنابيب ليخبروا الإطار بأنه يمكن الوثوق بهذا المحتوى.
شفرة المصدر لهذه الفئة بسيطة جدا:
https://github.com/angular/angular/blob/8.1.0/packages/platform-browser/src/security/dom_sanitization_service.ts#L148
تقدم هذه الخدمة مفهوم SafeValue
. في جوهرها ، هذا هو مجرد فئة المجمع لسلسلة. عندما يقوم Renderer
بإدراج قيمة من خلال الربط ، سواء كان innerHTML
أو @HostBinding
أو style
أو src
، يتم تشغيل القيمة من خلال طريقة sanitize
. إذا كان SafeValue
قد وصل بالفعل إلى هناك ، فسوف يقوم ببساطة بإرجاع السلسلة الملتفة ، وإلا فإنه ينظف المحتويات من تلقاء نفسه.
الزاوي ليس مكتبة تنظيف رمز البرامج الضارة المتخصصة. لذلك ، فإن إطار العمل يتبع بحق أقل المخاطر ويقلص كل ما يسبب القلق. يتحول رمز SVG إلى خطوط فارغة ، ويتم حذف الأنماط المضمّنة ، إلخ. ومع ذلك ، هناك مكتبات مصممة فقط لتأمين DOM ، واحدة منها هي DOMPurify:
https://github.com/cure53/DOMPurify

أنبوب SafeHtml الصحيح
بعد توصيل DOMPurify ، يمكننا أن نجعل أنبوبًا لا يميز المحتوى فقط بأنه آمن ، ولكن أيضًا ينظفه. للقيام بذلك ، قم DOMPurify.sanitize
قيمة الإدخال من خلال أسلوب DOMPurify.sanitize
، ثم قم DOMPurify.sanitize
أنها آمنة مع السياق المناسب:
@Pipe({name: 'dompurify'}) export class NgDompurifyPipe implements PipeTransform { constructor(private readonly domSanitizer: DomSanitizer) {} transform( value: {} | string | null, context: SecurityContext = SecurityContext.HTML, ): SafeValue | null { return this.bypassSecurityTrust(context, DOMPurify.sanitize(value)); } private bypassSecurityTrust( context: SecurityContext, purifiedValue: string, ): SafeValue | null { switch (context) { case SecurityContext.HTML: return this.domSanitizer.bypassSecurityTrustHtml(purifiedValue); case SecurityContext.STYLE: return this.domSanitizer.bypassSecurityTrustStyle(purifiedValue); case SecurityContext.SCRIPT: return this.domSanitizer.bypassSecurityTrustScript(purifiedValue); case SecurityContext.URL: return this.domSanitizer.bypassSecurityTrustUrl(purifiedValue); case SecurityContext.RESOURCE_URL: return this.domSanitizer.bypassSecurityTrustResourceUrl(purifiedValue); default: return null; } } }
هذا كل ما يتطلبه الأمر لتأمين التطبيق عند إدراج HTML في الصفحة. + 7 كيلو بايت gzip من DOMPurify و micropipe. ومع ذلك ، منذ صعدنا هنا ، سنحاول المضي قدمًا. يعني تجريد الطبقات أن Angular تقترح إمكانية إنشاء تطبيقاتك الخاصة. لماذا قم بإنشاء أنبوب ووضع علامة على المحتوى بأنه آمن إذا كنت تستطيع استخدام DOMPurify فورًا كـ DomSanitizer
؟

DomPurifyDomSanitizer
قم بإنشاء فئة ترث من DomSanitizer
وتفوض مسح القيم إلى DOMPurify. بالنسبة للمستقبل ، سنقوم على الفور بتنفيذ خدمة Sanitizer
وسنستخدمها في المواسير وفي DomSanitizer
. سيساعدنا ذلك في المستقبل ، حيث ستظهر نقطة دخول واحدة إلى DOMPurify. إن تطبيق SafeValue
وكل ما يتعلق بهذا المفهوم هو الرمز الخاص بـ Angular ، لذلك سيتعين علينا كتابته بأنفسنا. ومع ذلك ، كما رأينا في شفرة المصدر ، هذا ليس بالأمر الصعب.
تجدر الإشارة إلى أنه بالإضافة إلى HTML ، هناك SecurityContext
أخرى ، ولا سيما لتنظيف أنماط DOMPurify على هذا النحو ليست مناسبة. ومع ذلك ، يتم توسيع وظائفها باستخدام السنانير ، وأكثر على ذلك في وقت لاحق. بالإضافة إلى ذلك ، يمكن تمرير كائن التكوين إلى DOMPurify ، ويعد حقن التبعية مثاليًا لذلك. أضف رمزًا مميزًا إلى الكود الخاص بنا يسمح لنا بتوفير معلمات لـ DOMPurify:
@NgModule({
@Injectable({ providedIn: 'root', }) export class NgDompurifySanitizer extends Sanitizer { constructor( @Inject(DOMPURIFY_CONFIG) private readonly config: NgDompurifyConfig, ) { super(); } sanitize( _context: SecurityContext, value: {} | string | null, config: NgDompurifyConfig = this.config, ): string { return sanitize(String(value || ''), config); } }
يتم تنفيذ عمل DomSanitizer
مع الأنماط بطريقة لا يتلقى إلا قيمة قاعدة CSS عند الإدخال ، ولكن ليس اسم النمط نفسه. وبالتالي ، للسماح لمطهرنا بمسح الأنماط ، نحتاج إلى تزويده بطريقة ستتلقى سلسلة قيمة وتعيد سلسلة مسح. للقيام بذلك ، قم بإضافة رمز مميز آخر وقم بتعريفه افتراضيًا كدالة لا تؤدي إلى مسح السلسلة بأي شيء ، حيث لن نتحمل المسؤولية عن الأنماط الضارة المحتملة.
export const SANITIZE_STYLE = new InjectionToken<SanitizeStyle>( 'A function that sanitizes value for a CSS rule', { factory: () => () => '', providedIn: 'root', }, );
sanitize( context: SecurityContext, value: {} | string | null, config: NgDompurifyConfig = this.config, ): string { return context === SecurityContext.STYLE ? this.sanitizeStyle(String(value)) : sanitize(String(value || ''), config); }
وبهذه الطريقة ، سنقوم بمسح الأنماط المرتبطة مباشرة @HostBinding('style.*')
خلال [style.*]
أو @HostBinding('style.*')
. كما نتذكر ، فإن سلوك المطهر الزاوي المدمج هو إزالة جميع الأنماط المضمنة. بصراحة ، لا أعرف لماذا قرروا القيام بذلك ، لأنهم كتبوا طرقًا لإزالة قواعد CSS المشبوهة ، ولكن إذا احتجت إلى تنفيذ محرر WYSIWYG ، على سبيل المثال ، فلا يمكنك القيام بذلك دون استخدام الأنماط المضمنة. لحسن الحظ ، يسمح لك DOMPurify بإضافة السنانير لتوسيع قدرات المكتبة. يحتوي قسم الأمثلة حتى على رمز يقوم بمسح الأنماط في كل من عناصر DOM HTMLStyleElement
بأكملها.
الغياب
باستخدام DOMPurify.addHook()
يمكنك تسجيل الطرق التي تتلقى العنصر الحالي في مرحلة مختلفة من التنظيف. للاتصال بهم ، سنضيف الرمز الثالث والأخير. لدينا بالفعل طريقة لتنظيف الأنماط ، تحتاج فقط إلى تسميتها لجميع القواعد المضمنة. للقيام بذلك ، قم بتسجيل الخطاف المطلوب في مُنشئ خدمتنا. سنتجاوز جميع الخطافات التي جاءت إلينا باستخدام DI:
constructor( @Inject(DOMPURIFY_CONFIG) private readonly config: NgDompurifyConfig, @Inject(SANITIZE_STYLE) private readonly sanitizeStyle: SanitizeStyle, @Inject(DOMPURIFY_HOOKS) hooks: ReadonlyArray<NgDompurifyHook>, ) { super(); addHook('afterSanitizeAttributes', createAfterSanitizeAttributes(this.sanitizeStyle)); hooks.forEach(({name, hook}) => { addHook(name, hook); }); }
الخدمة التي نقدمها جاهزة وستبقى فقط لكتابة DomSanitizer
الخاص بك والذي سينقل محتوياته فقط للتنظيف. تحتاج أيضًا إلى تطبيق طرق bypassSecurityTrust***
، لكننا سبق أن bypassSecurityTrust***
عليها في شفرة المصدر الزاوي. الآن DOMPurify متاح في كل اتجاه ، باستخدام توجيه أو خدمة ، وتلقائيا في جميع أنحاء التطبيق.
استنتاج
اكتشفنا DomSanitizer
في Angular ولم يعد يخفي مشكلة إدراج HTML التعسفي. يمكنك الآن تضمين رسائل SVG و HTML بأمان من الخادم. لكي يعمل المطهر بشكل صحيح ، لا تزال بحاجة إلى تزويده بطريقة لتنظيف الأنماط. تعد CSS الضارة أقل شيوعًا بكثير من الأنواع الأخرى من الهجمات ، وأوقات تعبيرات CSS انتهت منذ فترة طويلة وما تفعله مع CSS يرجع إليك. يمكنك كتابة معالج بنفسك ، ويمكنك بصق وتخطي كل شيء لا يحتوي على أقواس ، بالتأكيد. أو يمكنك الغش قليلاً وإلقاء نظرة فاحصة على مصادر الزاوي. _sanitizeStyle
طريقة _sanitizeStyle
في الحزمة @angular/core
، بينما DomSanitizerImpl
موجود في @angular/platform-browser
، وعلى الرغم من أن هذه الطريقة خاصة في ɵ_sanitizeStyle
، فإن فريق Angular لا يتردد في الوصول إليها بالاسم الخاص ɵ_sanitizeStyle
. يمكننا أن نفعل الشيء نفسه ونقل هذه الطريقة إلى المطهر لدينا. وبالتالي ، نحصل على نفس المستوى من أنماط التنظيف مثل الافتراضي ، ولكن مع القدرة على استخدام الأنماط المضمنة عند إدراج كود HTML. لم يتغير اسم هذا الاستيراد الخاص منذ ظهوره في الإصدار السادس ، ولكن عليك أن تكون حذراً في مثل هذه الأشياء ، لا شيء يمنع فريق Angular من إعادة تسميته أو نقله أو حذفه في أي وقت.
الكود الموضح في المقال موجود على جيثب
تتوفر أيضًا في حزمة npm tinkoff / ng-dompurify:
https://www.npmjs.com/package/@tinkoff/ng-dompurify
يمكنك أن تلعب قليلا مع stackblitz مع عرض