Repository: twigphp/Twig

Issue #2615: html_attr escaping strategy
Created: 2018-01-26T18:46:30Z
I've looked high and low for an answer to this, and I've come to ask for clarification.
I am using escape('html_attr') filter for something like, <div data-stuff="{{ thing|escape('html_attr') }}" >. When escaping large strings, this suffers from poor performance.
The best reasons I can find for using html_attr instead of html is:

If you are not using quotes " to enclose your attribute (e.g. <div data-stuff=example>), then html_attr will work well because it will properly escapes spaces etc. I believe most people avoid this method because, well, it's ugly and horrible. But regardless, browsers do support it.
If you are using old versions of IE, it will consider a tick mark ` to terminate the attribute, and html_attr will escape the ticks, avoiding this (e.g. it won't run into the problem of <div data-stuff="example`example">, which would cause <div data-stuff="example" example> in old IE)

Can you give me an example of a string which would give undesired outcome if escaped using only the default escape('html'), besides the above two scenarios? Thank you for your time.

Duplicate issue #4322: Performance issues when escaping looooong value as `html_attr`
Created: 2024-09-18T11:04:38Z
Hi everyone, sorry if this issue is a duplicate, but I didn't find anything here.
For reference: symfony/ux#2178
With Symfony UX Map, when you create a Map with 1000 Marker and InfoWindow, the following code takes ~62% of the response time (near ~220 ms) to escape $string (here, a big JSON object which have been stringified):

  
    
      Twig/src/Runtime/EscaperRuntime.php
    
    
        Lines 240 to 303
      in
      126b2c9
    
  
  
    

        
          
                       case 'html_attr': 
        

        
          
                           if ('UTF-8' !== $charset) { 
        

        
          
                               $string = $this->convertEncoding($string, 'UTF-8', $charset); 
        

        
          
                           } 
        

        
          
            
        

        
          
                           if (!preg_match('//u', $string)) { 
        

        
          
                               throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); 
        

        
          
                           } 
        

        
          
            
        

        
          
                           $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { 
        

        
          
                               /** 
        

        
          
                                * This function is adapted from code coming from Zend Framework. 
        

        
          
                                * 
        

        
          
                                * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) 
        

        
          
                                * @license   https://framework.zend.com/license/new-bsd New BSD License 
        

        
          
                                */ 
        

        
          
                               $chr = $matches[0]; 
        

        
          
                               $ord = \ord($chr); 
        

        
          
            
        

        
          
                               /* 
        

        
          
                               * The following replaces characters undefined in HTML with the 
        

        
          
                               * hex entity for the Unicode replacement character. 
        

        
          
                               */ 
        

        
          
                               if (($ord <= 0x1F && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) { 
        

        
          
                                   return '&#xFFFD;'; 
        

        
          
                               } 
        

        
          
            
        

        
          
                               /* 
        

        
          
                               * Check if the current character to escape has a name entity we should 
        

        
          
                               * replace it with while grabbing the hex value of the character. 
        

        
          
                               */ 
        

        
          
                               if (1 === \strlen($chr)) { 
        

        
          
                                   /* 
        

        
          
                                   * While HTML supports far more named entities, the lowest common denominator 
        

        
          
                                   * has become HTML5's XML Serialisation which is restricted to the those named 
        

        
          
                                   * entities that XML supports. Using HTML entities would result in this error: 
        

        
          
                                   *     XML Parsing Error: undefined entity 
        

        
          
                                   */ 
        

        
          
                                   static $entityMap = [ 
        

        
          
                                       34 => '&quot;', /* quotation mark */ 
        

        
          
                                       38 => '&amp;',  /* ampersand */ 
        

        
          
                                       60 => '&lt;',   /* less-than sign */ 
        

        
          
                                       62 => '&gt;',   /* greater-than sign */ 
        

        
          
                                   ]; 
        

        
          
            
        

        
          
                                   if (isset($entityMap[$ord])) { 
        

        
          
                                       return $entityMap[$ord]; 
        

        
          
                                   } 
        

        
          
            
        

        
          
                                   return \sprintf('&#x%02X;', $ord); 
        

        
          
                               } 
        

        
          
            
        

        
          
                               /* 
        

        
          
                               * Per OWASP recommendations, we'll use hex entities for any other 
        

        
          
                               * characters where a named entity does not exist. 
        

        
          
                               */ 
        

        
          
                               return \sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); 
        

        
          
                           }, $string); 
        

        
          
            
        

        
          
                           if ('UTF-8' !== $charset) { 
        

        
          
                               $string = iconv('UTF-8', $charset, $string); 
        

        
          
                           } 
        

        
          
            
        

        
          
                           return $string; 
        
    
  


I.. don't know if its more an issue from Twig or a skill-issue from our StimulusAttributes usage.
But do you think something is optimizable here? is it safe to replace 'a very long stringified json object'|e('html_attr') with htmlentities(json_encode('...')? Because this combo improved performances by 62%!
Thanks!

Closing comment (2024-09-19T09:42:41Z): Thanks Google, in fact that's a duplicate of #2615.
I will try to send a doc PR asap :)
EDIT: opened #4324