"Fossies" - the Fresh Open Source Software Archive

Member "drupal-9.4.5/vendor/symfony/mime/Header/AbstractHeader.php" (20 Jul 2022, 10557 Bytes) of package /linux/www/drupal-9.4.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "AbstractHeader.php": 9.4.4_vs_9.4.5.

A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.


    1 <?php
    2 
    3 /*
    4  * This file is part of the Symfony package.
    5  *
    6  * (c) Fabien Potencier <fabien@symfony.com>
    7  *
    8  * For the full copyright and license information, please view the LICENSE
    9  * file that was distributed with this source code.
   10  */
   11 
   12 namespace Symfony\Component\Mime\Header;
   13 
   14 use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder;
   15 
   16 /**
   17  * An abstract base MIME Header.
   18  *
   19  * @author Chris Corbyn
   20  */
   21 abstract class AbstractHeader implements HeaderInterface
   22 {
   23     public const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
   24 
   25     private static $encoder;
   26 
   27     private $name;
   28     private $lineLength = 76;
   29     private $lang;
   30     private $charset = 'utf-8';
   31 
   32     public function __construct(string $name)
   33     {
   34         $this->name = $name;
   35     }
   36 
   37     public function setCharset(string $charset)
   38     {
   39         $this->charset = $charset;
   40     }
   41 
   42     public function getCharset(): ?string
   43     {
   44         return $this->charset;
   45     }
   46 
   47     /**
   48      * Set the language used in this Header.
   49      *
   50      * For example, for US English, 'en-us'.
   51      */
   52     public function setLanguage(string $lang)
   53     {
   54         $this->lang = $lang;
   55     }
   56 
   57     public function getLanguage(): ?string
   58     {
   59         return $this->lang;
   60     }
   61 
   62     public function getName(): string
   63     {
   64         return $this->name;
   65     }
   66 
   67     public function setMaxLineLength(int $lineLength)
   68     {
   69         $this->lineLength = $lineLength;
   70     }
   71 
   72     public function getMaxLineLength(): int
   73     {
   74         return $this->lineLength;
   75     }
   76 
   77     public function toString(): string
   78     {
   79         return $this->tokensToString($this->toTokens());
   80     }
   81 
   82     /**
   83      * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
   84      *
   85      * @param string $string  as displayed
   86      * @param bool   $shorten the first line to make remove for header name
   87      */
   88     protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string
   89     {
   90         // Treat token as exactly what was given
   91         $phraseStr = $string;
   92 
   93         // If it's not valid
   94         if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) {
   95             // .. but it is just ascii text, try escaping some characters
   96             // and make it a quoted-string
   97             if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
   98                 foreach (['\\', '"'] as $char) {
   99                     $phraseStr = str_replace($char, '\\'.$char, $phraseStr);
  100                 }
  101                 $phraseStr = '"'.$phraseStr.'"';
  102             } else {
  103                 // ... otherwise it needs encoding
  104                 // Determine space remaining on line if first line
  105                 if ($shorten) {
  106                     $usedLength = \strlen($header->getName().': ');
  107                 } else {
  108                     $usedLength = 0;
  109                 }
  110                 $phraseStr = $this->encodeWords($header, $string, $usedLength);
  111             }
  112         } elseif (str_contains($phraseStr, '(')) {
  113             foreach (['\\', '"'] as $char) {
  114                 $phraseStr = str_replace($char, '\\'.$char, $phraseStr);
  115             }
  116             $phraseStr = '"'.$phraseStr.'"';
  117         }
  118 
  119         return $phraseStr;
  120     }
  121 
  122     /**
  123      * Encode needed word tokens within a string of input.
  124      */
  125     protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string
  126     {
  127         $value = '';
  128         $tokens = $this->getEncodableWordTokens($input);
  129         foreach ($tokens as $token) {
  130             // See RFC 2822, Sect 2.2 (really 2.2 ??)
  131             if ($this->tokenNeedsEncoding($token)) {
  132                 // Don't encode starting WSP
  133                 $firstChar = substr($token, 0, 1);
  134                 switch ($firstChar) {
  135                     case ' ':
  136                     case "\t":
  137                         $value .= $firstChar;
  138                         $token = substr($token, 1);
  139                 }
  140 
  141                 if (-1 == $usedLength) {
  142                     $usedLength = \strlen($header->getName().': ') + \strlen($value);
  143                 }
  144                 $value .= $this->getTokenAsEncodedWord($token, $usedLength);
  145             } else {
  146                 $value .= $token;
  147             }
  148         }
  149 
  150         return $value;
  151     }
  152 
  153     protected function tokenNeedsEncoding(string $token): bool
  154     {
  155         return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
  156     }
  157 
  158     /**
  159      * Splits a string into tokens in blocks of words which can be encoded quickly.
  160      *
  161      * @return string[]
  162      */
  163     protected function getEncodableWordTokens(string $string): array
  164     {
  165         $tokens = [];
  166         $encodedToken = '';
  167         // Split at all whitespace boundaries
  168         foreach (preg_split('~(?=[\t ])~', $string) as $token) {
  169             if ($this->tokenNeedsEncoding($token)) {
  170                 $encodedToken .= $token;
  171             } else {
  172                 if ('' !== $encodedToken) {
  173                     $tokens[] = $encodedToken;
  174                     $encodedToken = '';
  175                 }
  176                 $tokens[] = $token;
  177             }
  178         }
  179         if ('' !== $encodedToken) {
  180             $tokens[] = $encodedToken;
  181         }
  182 
  183         return $tokens;
  184     }
  185 
  186     /**
  187      * Get a token as an encoded word for safe insertion into headers.
  188      */
  189     protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string
  190     {
  191         if (null === self::$encoder) {
  192             self::$encoder = new QpMimeHeaderEncoder();
  193         }
  194 
  195         // Adjust $firstLineOffset to account for space needed for syntax
  196         $charsetDecl = $this->charset;
  197         if (null !== $this->lang) {
  198             $charsetDecl .= '*'.$this->lang;
  199         }
  200         $encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??=');
  201 
  202         if ($firstLineOffset >= 75) {
  203             // Does this logic need to be here?
  204             $firstLineOffset = 0;
  205         }
  206 
  207         $encodedTextLines = explode("\r\n",
  208             self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength)
  209         );
  210 
  211         if ('iso-2022-jp' !== strtolower($this->charset)) {
  212             // special encoding for iso-2022-jp using mb_encode_mimeheader
  213             foreach ($encodedTextLines as $lineNum => $line) {
  214                 $encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?=';
  215             }
  216         }
  217 
  218         return implode("\r\n ", $encodedTextLines);
  219     }
  220 
  221     /**
  222      * Generates tokens from the given string which include CRLF as individual tokens.
  223      *
  224      * @return string[]
  225      */
  226     protected function generateTokenLines(string $token): array
  227     {
  228         return preg_split('~(\r\n)~', $token, -1, \PREG_SPLIT_DELIM_CAPTURE);
  229     }
  230 
  231     /**
  232      * Generate a list of all tokens in the final header.
  233      */
  234     protected function toTokens(string $string = null): array
  235     {
  236         if (null === $string) {
  237             $string = $this->getBodyAsString();
  238         }
  239 
  240         $tokens = [];
  241         // Generate atoms; split at all invisible boundaries followed by WSP
  242         foreach (preg_split('~(?=[ \t])~', $string) as $token) {
  243             $newTokens = $this->generateTokenLines($token);
  244             foreach ($newTokens as $newToken) {
  245                 $tokens[] = $newToken;
  246             }
  247         }
  248 
  249         return $tokens;
  250     }
  251 
  252     /**
  253      * Takes an array of tokens which appear in the header and turns them into
  254      * an RFC 2822 compliant string, adding FWSP where needed.
  255      *
  256      * @param string[] $tokens
  257      */
  258     private function tokensToString(array $tokens): string
  259     {
  260         $lineCount = 0;
  261         $headerLines = [];
  262         $headerLines[] = $this->name.': ';
  263         $currentLine = &$headerLines[$lineCount++];
  264 
  265         // Build all tokens back into compliant header
  266         foreach ($tokens as $i => $token) {
  267             // Line longer than specified maximum or token was just a new line
  268             if (("\r\n" === $token) ||
  269                 ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
  270                 && '' !== $currentLine) {
  271                 $headerLines[] = '';
  272                 $currentLine = &$headerLines[$lineCount++];
  273             }
  274 
  275             // Append token to the line
  276             if ("\r\n" !== $token) {
  277                 $currentLine .= $token;
  278             }
  279         }
  280 
  281         // Implode with FWS (RFC 2822, 2.2.3)
  282         return implode("\r\n", $headerLines);
  283     }
  284 }