XOOPS RMCommon Utilities  2.1.8.91RC
 All Classes Namespaces Files Functions Variables
AbstractHeader.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of SwiftMailer.
5  * (c) 2004-2009 Chris Corbyn
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10 
11 //@require 'Swift/Mime/Header.php';
12 //@require 'Swift/Mime/HeaderEncoder.php';
13 //@require 'Swift/RfcComplianceException.php';
14 
22 {
23 
29  private $_specials = array();
30 
36  private $_grammar = array();
37 
43  private $_name;
44 
50  private $_encoder;
51 
57  private $_lineLength = 78;
58 
63  private $_lang;
64 
70  private $_charset = 'utf-8';
71 
77  private $_cachedValue = null;
78 
83  public function setCharset($charset)
84  {
85  $this->clearCachedValueIf($charset != $this->_charset);
86  $this->_charset = $charset;
87  if (isset($this->_encoder))
88  {
89  $this->_encoder->charsetChanged($charset);
90  }
91  }
92 
97  public function getCharset()
98  {
99  return $this->_charset;
100  }
101 
108  public function setLanguage($lang)
109  {
110  $this->clearCachedValueIf($this->_lang != $lang);
111  $this->_lang = $lang;
112  }
113 
118  public function getLanguage()
119  {
120  return $this->_lang;
121  }
122 
127  public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
128  {
129  $this->_encoder = $encoder;
130  $this->setCachedValue(null);
131  }
132 
137  public function getEncoder()
138  {
139  return $this->_encoder;
140  }
141 
146  public function getFieldName()
147  {
148  return $this->_name;
149  }
150 
155  public function setMaxLineLength($lineLength)
156  {
157  $this->clearCachedValueIf($this->_lineLength != $lineLength);
158  $this->_lineLength = $lineLength;
159  }
160 
165  public function getMaxLineLength()
166  {
167  return $this->_lineLength;
168  }
169 
175  public function toString()
176  {
177  return $this->_tokensToString($this->toTokens());
178  }
179 
187  public function __toString()
188  {
189  return $this->toString();
190  }
191 
192  // -- Points of extension
193 
199  protected function setFieldName($name)
200  {
201  $this->_name = $name;
202  }
203 
208  protected function initializeGrammar()
209  {
210  $this->_specials = array(
211  '(', ')', '<', '>', '[', ']',
212  ':', ';', '@', ',', '.', '"'
213  );
214 
215  /*** Refer to RFC 2822 for ABNF grammar ***/
216 
217  //All basic building blocks
218  $this->_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]';
219  $this->_grammar['WSP'] = '[ \t]';
220  $this->_grammar['CRLF'] = '(?:\r\n)';
221  $this->_grammar['FWS'] = '(?:(?:' . $this->_grammar['WSP'] . '*' .
222  $this->_grammar['CRLF'] . ')?' . $this->_grammar['WSP'] . ')';
223  $this->_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]';
224  $this->_grammar['quoted-pair'] = '(?:\\\\' . $this->_grammar['text'] . ')';
225  $this->_grammar['ctext'] = '(?:' . $this->_grammar['NO-WS-CTL'] .
226  '|[\x21-\x27\x2A-\x5B\x5D-\x7E])';
227  //Uses recursive PCRE (?1) -- could be a weak point??
228  $this->_grammar['ccontent'] = '(?:' . $this->_grammar['ctext'] . '|' .
229  $this->_grammar['quoted-pair'] . '|(?1))';
230  $this->_grammar['comment'] = '(\((?:' . $this->_grammar['FWS'] . '|' .
231  $this->_grammar['ccontent']. ')*' . $this->_grammar['FWS'] . '?\))';
232  $this->_grammar['CFWS'] = '(?:(?:' . $this->_grammar['FWS'] . '?' .
233  $this->_grammar['comment'] . ')*(?:(?:' . $this->_grammar['FWS'] . '?' .
234  $this->_grammar['comment'] . ')|' . $this->_grammar['FWS'] . '))';
235  $this->_grammar['qtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] .
236  '|[\x21\x23-\x5B\x5D-\x7E])';
237  $this->_grammar['qcontent'] = '(?:' . $this->_grammar['qtext'] . '|' .
238  $this->_grammar['quoted-pair'] . ')';
239  $this->_grammar['quoted-string'] = '(?:' . $this->_grammar['CFWS'] . '?"' .
240  '(' . $this->_grammar['FWS'] . '?' . $this->_grammar['qcontent'] . ')*' .
241  $this->_grammar['FWS'] . '?"' . $this->_grammar['CFWS'] . '?)';
242  $this->_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]';
243  $this->_grammar['atom'] = '(?:' . $this->_grammar['CFWS'] . '?' .
244  $this->_grammar['atext'] . '+' . $this->_grammar['CFWS'] . '?)';
245  $this->_grammar['dot-atom-text'] = '(?:' . $this->_grammar['atext'] . '+' .
246  '(\.' . $this->_grammar['atext'] . '+)*)';
247  $this->_grammar['dot-atom'] = '(?:' . $this->_grammar['CFWS'] . '?' .
248  $this->_grammar['dot-atom-text'] . '+' . $this->_grammar['CFWS'] . '?)';
249  $this->_grammar['word'] = '(?:' . $this->_grammar['atom'] . '|' .
250  $this->_grammar['quoted-string'] . ')';
251  $this->_grammar['phrase'] = '(?:' . $this->_grammar['word'] . '+?)';
252  $this->_grammar['no-fold-quote'] = '(?:"(?:' . $this->_grammar['qtext'] .
253  '|' . $this->_grammar['quoted-pair'] . ')*")';
254  $this->_grammar['dtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] .
255  '|[\x21-\x5A\x5E-\x7E])';
256  $this->_grammar['no-fold-literal'] = '(?:\[(?:' . $this->_grammar['dtext'] .
257  '|' . $this->_grammar['quoted-pair'] . ')*\])';
258 
259  //Message IDs
260  $this->_grammar['id-left'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' .
261  $this->_grammar['no-fold-quote'] . ')';
262  $this->_grammar['id-right'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' .
263  $this->_grammar['no-fold-literal'] . ')';
264 
265  //Addresses, mailboxes and paths
266  $this->_grammar['local-part'] = '(?:' . $this->_grammar['dot-atom'] . '|' .
267  $this->_grammar['quoted-string'] . ')';
268  $this->_grammar['dcontent'] = '(?:' . $this->_grammar['dtext'] . '|' .
269  $this->_grammar['quoted-pair'] . ')';
270  $this->_grammar['domain-literal'] = '(?:' . $this->_grammar['CFWS'] . '?\[(' .
271  $this->_grammar['FWS'] . '?' . $this->_grammar['dcontent'] . ')*?' .
272  $this->_grammar['FWS'] . '?\]' . $this->_grammar['CFWS'] . '?)';
273  $this->_grammar['domain'] = '(?:' . $this->_grammar['dot-atom'] . '|' .
274  $this->_grammar['domain-literal'] . ')';
275  $this->_grammar['addr-spec'] = '(?:' . $this->_grammar['local-part'] . '@' .
276  $this->_grammar['domain'] . ')';
277  }
278 
284  protected function getGrammar($name)
285  {
286  if (array_key_exists($name, $this->_grammar))
287  {
288  return $this->_grammar[$name];
289  }
290  else
291  {
293  "No such grammar '" . $name . "' defined."
294  );
295  }
296  }
297 
305  protected function escapeSpecials($token, $include = array(),
306  $exclude = array())
307  {
308  foreach (
309  array_merge(array('\\'), array_diff($this->_specials, $exclude), $include) as $char)
310  {
311  $token = str_replace($char, '\\' . $char, $token);
312  }
313  return $token;
314  }
315 
325  protected function createPhrase(Swift_Mime_Header $header, $string, $charset,
326  Swift_Mime_HeaderEncoder $encoder = null, $shorten = false)
327  {
328  //Treat token as exactly what was given
329  $phraseStr = $string;
330  //If it's not valid
331  if (!preg_match('/^' . $this->_grammar['phrase'] . '$/D', $phraseStr))
332  {
333  // .. but it is just ascii text, try escaping some characters
334  // and make it a quoted-string
335  if (preg_match('/^' . $this->_grammar['text'] . '*$/D', $phraseStr))
336  {
337  $phraseStr = $this->escapeSpecials(
338  $phraseStr, array('"'), $this->_specials
339  );
340  $phraseStr = '"' . $phraseStr . '"';
341  }
342  else // ... otherwise it needs encoding
343  {
344  //Determine space remaining on line if first line
345  if ($shorten)
346  {
347  $usedLength = strlen($header->getFieldName() . ': ');
348  }
349  else
350  {
351  $usedLength = 0;
352  }
353  $phraseStr = $this->encodeWords($header, $string, $usedLength);
354  }
355  }
356 
357  return $phraseStr;
358  }
359 
366  protected function encodeWords(Swift_Mime_Header $header, $input,
367  $usedLength = -1)
368  {
369  $value = '';
370 
371  $tokens = $this->getEncodableWordTokens($input);
372 
373  foreach ($tokens as $token)
374  {
375  //See RFC 2822, Sect 2.2 (really 2.2 ??)
376  if ($this->tokenNeedsEncoding($token))
377  {
378  //Don't encode starting WSP
379  $firstChar = substr($token, 0, 1);
380  switch($firstChar)
381  {
382  case ' ':
383  case "\t":
384  $value .= $firstChar;
385  $token = substr($token, 1);
386  }
387 
388  if (-1 == $usedLength)
389  {
390  $usedLength = strlen($header->getFieldName() . ': ') + strlen($value);
391  }
392  $value .= $this->getTokenAsEncodedWord($token, $usedLength);
393 
394  $header->setMaxLineLength(76); //Forefully override
395  }
396  else
397  {
398  $value .= $token;
399  }
400  }
401 
402  return $value;
403  }
404 
410  protected function tokenNeedsEncoding($token)
411  {
412  return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
413  }
414 
420  protected function getEncodableWordTokens($string)
421  {
422  $tokens = array();
423 
424  $encodedToken = '';
425  //Split at all whitespace boundaries
426  foreach (preg_split('~(?=[\t ])~', $string) as $token)
427  {
428  if ($this->tokenNeedsEncoding($token))
429  {
430  $encodedToken .= $token;
431  }
432  else
433  {
434  if (strlen($encodedToken) > 0)
435  {
436  $tokens[] = $encodedToken;
437  $encodedToken = '';
438  }
439  $tokens[] = $token;
440  }
441  }
442  if (strlen($encodedToken))
443  {
444  $tokens[] = $encodedToken;
445  }
446 
447  return $tokens;
448  }
449 
456  protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
457  {
458  //Adjust $firstLineOffset to account for space needed for syntax
459  $charsetDecl = $this->_charset;
460  if (isset($this->_lang))
461  {
462  $charsetDecl .= '*' . $this->_lang;
463  }
464  $encodingWrapperLength = strlen(
465  '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??='
466  );
467 
468  if ($firstLineOffset >= 75) //Does this logic need to be here?
469  {
470  $firstLineOffset = 0;
471  }
472 
473  $encodedTextLines = explode("\r\n",
474  $this->_encoder->encodeString(
475  $token, $firstLineOffset, 75 - $encodingWrapperLength
476  )
477  );
478 
479  foreach ($encodedTextLines as $lineNum => $line)
480  {
481  $encodedTextLines[$lineNum] = '=?' . $charsetDecl .
482  '?' . $this->_encoder->getName() .
483  '?' . $line . '?=';
484  }
485 
486  return implode("\r\n ", $encodedTextLines);
487  }
488 
495  protected function generateTokenLines($token)
496  {
497  return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
498  }
499 
505  protected function setCachedValue($value)
506  {
507  $this->_cachedValue = $value;
508  }
509 
515  protected function getCachedValue()
516  {
517  return $this->_cachedValue;
518  }
519 
525  protected function clearCachedValueIf($condition)
526  {
527  if ($condition)
528  {
529  $this->setCachedValue(null);
530  }
531  }
532 
533  // -- Private methods
534 
541  protected function toTokens($string = null)
542  {
543  if (is_null($string))
544  {
545  $string = $this->getFieldBody();
546  }
547 
548  $tokens = array();
549 
550  //Generate atoms; split at all invisible boundaries followed by WSP
551  foreach (preg_split('~(?=[ \t])~', $string) as $token)
552  {
553  $tokens = array_merge($tokens, $this->generateTokenLines($token));
554  }
555 
556  return $tokens;
557  }
558 
566  private function _tokensToString(array $tokens)
567  {
568  $lineCount = 0;
569  $headerLines = array();
570  $headerLines[] = $this->_name . ': ';
571  $currentLine =& $headerLines[$lineCount++];
572 
573  //Build all tokens back into compliant header
574  foreach ($tokens as $i => $token)
575  {
576  //Line longer than specified maximum or token was just a new line
577  if (("\r\n" == $token) ||
578  ($i > 0 && strlen($currentLine . $token) > $this->_lineLength)
579  && 0 < strlen($currentLine))
580  {
581  $headerLines[] = '';
582  $currentLine =& $headerLines[$lineCount++];
583  }
584 
585  //Append token to the line
586  if ("\r\n" != $token)
587  {
588  $currentLine .= $token;
589  }
590  }
591 
592  //Implode with FWS (RFC 2822, 2.2.3)
593  return implode("\r\n", $headerLines) . "\r\n";
594  }
595 
596 }