PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/Jyxo/Mail/Encoding.php

http://github.com/jyxo/php
PHP | 201 lines | 97 code | 26 blank | 78 comment | 9 complexity | a21740e9480f69de34fa6d9f00c5f1b7 MD5 | raw file
  1. <?php declare(strict_types = 1);
  2. /**
  3. * Jyxo PHP Library
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file license.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * https://github.com/jyxo/php/blob/master/license.txt
  11. */
  12. namespace Jyxo\Mail;
  13. use InvalidArgumentException;
  14. use Jyxo\StringUtil;
  15. use LogicException;
  16. use function base64_encode;
  17. use function chunk_split;
  18. use function explode;
  19. use function ord;
  20. use function preg_replace_callback;
  21. use function sprintf;
  22. use function strlen;
  23. use function strrpos;
  24. use function substr;
  25. use function trim;
  26. /**
  27. * List of possible content encodings.
  28. *
  29. * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
  30. * @license https://github.com/jyxo/php/blob/master/license.txt
  31. * @author Jaroslav HanslĂ­k
  32. */
  33. class Encoding
  34. {
  35. /**
  36. * 8-bit encoding.
  37. */
  38. public const BIT8 = '8bit';
  39. /**
  40. * 7-bit encoding.
  41. */
  42. public const BIT7 = '7bit';
  43. /**
  44. * Binary encoding.
  45. */
  46. public const BINARY = 'binary';
  47. /**
  48. * Base64 encoding.
  49. */
  50. public const BASE64 = 'base64';
  51. /**
  52. * Quoted printable encoding.
  53. */
  54. public const QUOTED_PRINTABLE = 'quoted-printable';
  55. /**
  56. * Constructor preventing from creating instances.
  57. *
  58. * @throws LogicException When trying to create an instance
  59. */
  60. final public function __construct()
  61. {
  62. throw new LogicException(sprintf('Cannot create an instance of a static class %s.', static::class));
  63. }
  64. /**
  65. * Checks if the given encoding is compatible.
  66. *
  67. * @param string $encoding Encoding name
  68. * @return bool
  69. */
  70. public static function isCompatible(string $encoding): bool
  71. {
  72. static $encodings = [
  73. self::BIT7 => true,
  74. self::BIT8 => true,
  75. self::BINARY => true,
  76. self::BASE64 => true,
  77. self::QUOTED_PRINTABLE => true,
  78. ];
  79. return isset($encodings[$encoding]);
  80. }
  81. /**
  82. * Encodes a string using the given encoding.
  83. *
  84. * @param string $string Input string
  85. * @param string $encoding Encoding name
  86. * @param int $lineLength Line length
  87. * @param string $lineEnd Line ending
  88. * @return string
  89. * @throws InvalidArgumentException If an incompatible encoding was provided
  90. */
  91. public static function encode(string $string, string $encoding, int $lineLength, string $lineEnd): string
  92. {
  93. switch ($encoding) {
  94. case self::BASE64:
  95. return self::encodeBase64($string, $lineLength, $lineEnd);
  96. case self::BIT7:
  97. // Break missing intentionally
  98. case self::BIT8:
  99. return StringUtil::fixLineEnding(trim($string), $lineEnd) . $lineEnd;
  100. case self::QUOTED_PRINTABLE:
  101. return self::encodeQuotedPrintable($string, $lineLength, $lineEnd);
  102. case self::BINARY:
  103. return $string;
  104. default:
  105. throw new InvalidArgumentException(sprintf('Incompatible encoding %s.', $encoding));
  106. }
  107. }
  108. /**
  109. * Encodes a string using the quoted-printable encoding.
  110. *
  111. * @param string $string Input string
  112. * @param int $lineLength Line length
  113. * @param string $lineEnd Line ending
  114. * @return string
  115. */
  116. private static function encodeQuotedPrintable(string $string, int $lineLength, string $lineEnd): string
  117. {
  118. $encoded = StringUtil::fixLineEnding(trim($string), $lineEnd);
  119. // Replaces all high ASCII characters, control codes and '='
  120. $encoded = preg_replace_callback('~([\000-\010\013\014\016-\037\075\177-\377])~', static function ($matches) {
  121. return '=' . sprintf('%02X', ord($matches[1]));
  122. }, $encoded);
  123. // Replaces tabs and spaces if on line ends
  124. $encoded = preg_replace_callback('~([\011\040])' . $lineEnd . '~', static function ($matches) use ($lineEnd) {
  125. return '=' . sprintf('%02X', ord($matches[1])) . $lineEnd;
  126. }, $encoded);
  127. $output = '';
  128. $lines = explode($lineEnd, $encoded);
  129. // Release from memory
  130. unset($encoded);
  131. foreach ($lines as $line) {
  132. // Line length is less than maximum
  133. if (strlen($line) <= $lineLength) {
  134. $output .= $line . $lineEnd;
  135. continue;
  136. }
  137. do {
  138. $partLength = strlen($line);
  139. if ($partLength > $lineLength) {
  140. $partLength = $lineLength;
  141. }
  142. // Cannot break a line in the middle of a character
  143. $pos = strrpos(substr($line, 0, $partLength), '=');
  144. if (($pos !== false) && ($pos >= $partLength - 2)) {
  145. $partLength = $pos;
  146. }
  147. // If the last char is a break, move one character backwards
  148. if (($partLength > 0) && ($line[$partLength - 1] === ' ')) {
  149. $partLength--;
  150. }
  151. // Saves string parts, trims the string and continues
  152. $output .= substr($line, 0, $partLength);
  153. $line = substr($line, $partLength);
  154. // We are in the middle of a line
  155. if (!empty($line)) {
  156. $output .= '=';
  157. }
  158. $output .= $lineEnd;
  159. } while (!empty($line));
  160. }
  161. return $output;
  162. }
  163. /**
  164. * Encodes a string using the base64 encoding.
  165. *
  166. * @param string $string Input string
  167. * @param int $lineLength Line length
  168. * @param string $lineEnd Line ending
  169. * @return string
  170. */
  171. private static function encodeBase64(string $string, int $lineLength, string $lineEnd): string
  172. {
  173. return trim(chunk_split(base64_encode($string), $lineLength, $lineEnd));
  174. }
  175. }