PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Mime.php

https://bitbucket.org/moodle/moodle
PHP | 397 lines | 241 code | 56 blank | 100 comment | 34 complexity | 374fd7136185a9832579165c438f2469 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  1. <?php
  2. /**
  3. * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
  4. *
  5. * See the enclosed file LICENSE for license information (LGPL). If you
  6. * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  7. *
  8. * @category Horde
  9. * @copyright 1999-2017 Horde LLC
  10. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11. * @package Mime
  12. */
  13. /**
  14. * Provide methods for dealing with MIME encoding (RFC 2045-2049);
  15. *
  16. * @author Chuck Hagenbuch <chuck@horde.org>
  17. * @author Michael Slusarz <slusarz@horde.org>
  18. * @category Horde
  19. * @copyright 1999-2017 Horde LLC
  20. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  21. * @package Mime
  22. */
  23. class Horde_Mime
  24. {
  25. /**
  26. * The RFC defined EOL string.
  27. *
  28. * @var string
  29. */
  30. const EOL = "\r\n";
  31. /**
  32. * Use windows-1252 charset when decoding ISO-8859-1 data?
  33. * HTML 5 requires this behavior, so it is the default.
  34. *
  35. * @var boolean
  36. */
  37. public static $decodeWindows1252 = true;
  38. /**
  39. * Determines if a string contains 8-bit (non US-ASCII) characters.
  40. *
  41. * @param string $string The string to check.
  42. * @param string $charset The charset of the string. Defaults to
  43. * US-ASCII. (@deprecated)
  44. *
  45. * @return boolean True if string contains non 7-bit characters.
  46. */
  47. public static function is8bit($string, $charset = null)
  48. {
  49. $string = strval($string);
  50. for ($i = 0, $len = strlen($string); $i < $len; ++$i) {
  51. if (ord($string[$i]) > 127) {
  52. return true;
  53. }
  54. }
  55. return false;
  56. }
  57. /**
  58. * MIME encodes a string (RFC 2047).
  59. *
  60. * @param string $text The text to encode (UTF-8).
  61. * @param string $charset The character set to encode to.
  62. *
  63. * @return string The MIME encoded string (US-ASCII).
  64. */
  65. public static function encode($text, $charset = 'UTF-8')
  66. {
  67. $charset = Horde_String::lower($charset);
  68. $text = Horde_String::convertCharset($text, 'UTF-8', $charset);
  69. $encoded = $is_encoded = false;
  70. $lwsp = $word = null;
  71. $out = '';
  72. /* 0 = word unencoded
  73. * 1 = word encoded
  74. * 2 = spaces */
  75. $parts = array();
  76. /* Tokenize string. */
  77. for ($i = 0, $len = strlen($text); $i < $len; ++$i) {
  78. switch ($text[$i]) {
  79. case "\t":
  80. case "\r":
  81. case "\n":
  82. if (!is_null($word)) {
  83. $parts[] = array(intval($encoded), $word, $i - $word);
  84. $word = null;
  85. } elseif (!is_null($lwsp)) {
  86. $parts[] = array(2, $lwsp, $i - $lwsp);
  87. $lwsp = null;
  88. }
  89. $parts[] = array(0, $i, 1);
  90. break;
  91. case ' ':
  92. if (!is_null($word)) {
  93. $parts[] = array(intval($encoded), $word, $i - $word);
  94. $word = null;
  95. }
  96. if (is_null($lwsp)) {
  97. $lwsp = $i;
  98. }
  99. break;
  100. default:
  101. if (is_null($word)) {
  102. $encoded = false;
  103. $word = $i;
  104. if (!is_null($lwsp)) {
  105. $parts[] = array(2, $lwsp, $i - $lwsp);
  106. $lwsp = null;
  107. }
  108. /* Check for MIME encoding delimiter. Encode it if
  109. * found. */
  110. if (($text[$i] === '=') &&
  111. (($i + 1) < $len) &&
  112. ($text[$i +1] === '?')) {
  113. ++$i;
  114. $encoded = $is_encoded = true;
  115. }
  116. }
  117. /* Check for 8-bit characters or control characters. */
  118. if (!$encoded) {
  119. $c = ord($text[$i]);
  120. if ($encoded = (($c & 0x80) || ($c < 32))) {
  121. $is_encoded = true;
  122. }
  123. }
  124. break;
  125. }
  126. }
  127. if (!$is_encoded) {
  128. return $text;
  129. }
  130. if (is_null($lwsp)) {
  131. $parts[] = array(intval($encoded), $word, $len);
  132. } else {
  133. $parts[] = array(2, $lwsp, $len);
  134. }
  135. /* Combine parts into MIME encoded string. */
  136. for ($i = 0, $cnt = count($parts); $i < $cnt; ++$i) {
  137. $val = $parts[$i];
  138. switch ($val[0]) {
  139. case 0:
  140. case 2:
  141. $out .= substr($text, $val[1], $val[2]);
  142. break;
  143. case 1:
  144. $j = $i;
  145. for ($k = $i + 1; $k < $cnt; ++$k) {
  146. switch ($parts[$k][0]) {
  147. case 0:
  148. break 2;
  149. case 1:
  150. $i = $k;
  151. break;
  152. }
  153. }
  154. $encode = '';
  155. for (; $j <= $i; ++$j) {
  156. $encode .= substr($text, $parts[$j][1], $parts[$j][2]);
  157. }
  158. $delim = '=?' . $charset . '?b?';
  159. $e_parts = explode(
  160. self::EOL,
  161. rtrim(
  162. chunk_split(
  163. base64_encode($encode),
  164. /* strlen($delim) + 2 = space taken by MIME
  165. * delimiter */
  166. intval((75 - strlen($delim) + 2) / 4) * 4
  167. )
  168. )
  169. );
  170. $tmp = array();
  171. foreach ($e_parts as $val) {
  172. $tmp[] = $delim . $val . '?=';
  173. }
  174. $out .= implode(' ', $tmp);
  175. break;
  176. }
  177. }
  178. return rtrim($out);
  179. }
  180. /**
  181. * Decodes a MIME encoded (RFC 2047) string.
  182. *
  183. * @param string $string The MIME encoded text.
  184. *
  185. * @return string The decoded text.
  186. */
  187. public static function decode($string)
  188. {
  189. $old_pos = 0;
  190. $out = '';
  191. while (($pos = strpos($string, '=?', $old_pos)) !== false) {
  192. /* Save any preceding text, if it is not LWSP between two
  193. * encoded words. */
  194. $pre = substr($string, $old_pos, $pos - $old_pos);
  195. if (!$old_pos ||
  196. (strspn($pre, " \t\n\r") != strlen($pre))) {
  197. $out .= $pre;
  198. }
  199. /* Search for first delimiting question mark (charset). */
  200. if (($d1 = strpos($string, '?', $pos + 2)) === false) {
  201. break;
  202. }
  203. $orig_charset = substr($string, $pos + 2, $d1 - $pos - 2);
  204. if (self::$decodeWindows1252 &&
  205. (Horde_String::lower($orig_charset) == 'iso-8859-1')) {
  206. $orig_charset = 'windows-1252';
  207. }
  208. /* Search for second delimiting question mark (encoding). */
  209. if (($d2 = strpos($string, '?', $d1 + 1)) === false) {
  210. break;
  211. }
  212. $encoding = substr($string, $d1 + 1, $d2 - $d1 - 1);
  213. /* Search for end of encoded data. */
  214. if (($end = strpos($string, '?=', $d2 + 1)) === false) {
  215. break;
  216. }
  217. $encoded_text = substr($string, $d2 + 1, $end - $d2 - 1);
  218. switch ($encoding) {
  219. case 'Q':
  220. case 'q':
  221. $out .= Horde_String::convertCharset(
  222. quoted_printable_decode(
  223. str_replace('_', ' ', $encoded_text)
  224. ),
  225. $orig_charset,
  226. 'UTF-8'
  227. );
  228. break;
  229. case 'B':
  230. case 'b':
  231. $out .= Horde_String::convertCharset(
  232. base64_decode($encoded_text),
  233. $orig_charset,
  234. 'UTF-8'
  235. );
  236. break;
  237. default:
  238. // Ignore unknown encoding.
  239. break;
  240. }
  241. $old_pos = $end + 2;
  242. }
  243. return $out . substr($string, $old_pos);
  244. }
  245. /* Deprecated methods. */
  246. /**
  247. * @deprecated Use Horde_Mime_Headers_MessageId::create() instead.
  248. */
  249. public static function generateMessageId()
  250. {
  251. return Horde_Mime_Headers_MessageId::create()->value;
  252. }
  253. /**
  254. * @deprecated Use Horde_Mime_Uudecode instead.
  255. */
  256. public static function uudecode($input)
  257. {
  258. $uudecode = new Horde_Mime_Uudecode($input);
  259. return iterator_to_array($uudecode);
  260. }
  261. /**
  262. * @deprecated
  263. */
  264. public static $brokenRFC2231 = false;
  265. /**
  266. * @deprecated
  267. */
  268. const MIME_PARAM_QUOTED = '/[\x01-\x20\x22\x28\x29\x2c\x2f\x3a-\x40\x5b-\x5d]/';
  269. /**
  270. * @deprecated Use Horde_Mime_Headers_ContentParam#encode() instead.
  271. */
  272. public static function encodeParam($name, $val, array $opts = array())
  273. {
  274. $cp = new Horde_Mime_Headers_ContentParam(
  275. 'UNUSED',
  276. array($name => $val)
  277. );
  278. return $cp->encode(array_merge(array(
  279. 'broken_rfc2231' => self::$brokenRFC2231
  280. ), $opts));
  281. }
  282. /**
  283. * @deprecated Use Horde_Mime_Headers_ELement_ContentParam instead.
  284. */
  285. public static function decodeParam($type, $data)
  286. {
  287. $cp = new Horde_Mime_Headers_ContentParam(
  288. 'UNUSED',
  289. $data
  290. );
  291. if (strlen($cp->value)) {
  292. $val = $cp->value;
  293. } else {
  294. $val = (Horde_String::lower($type) == 'content-type')
  295. ? 'text/plain'
  296. : 'attachment';
  297. }
  298. return array(
  299. 'params' => $cp->params,
  300. 'val' => $val
  301. );
  302. }
  303. /**
  304. * @deprecated Use Horde_Mime_Id instead.
  305. */
  306. public static function mimeIdArithmetic($id, $action, $options = array())
  307. {
  308. $id_ob = new Horde_Mime_Id($id);
  309. switch ($action) {
  310. case 'down':
  311. $action = $id_ob::ID_DOWN;
  312. break;
  313. case 'next':
  314. $action = $id_ob::ID_NEXT;
  315. break;
  316. case 'prev':
  317. $action = $id_ob::ID_PREV;
  318. break;
  319. case 'up':
  320. $action = $id_ob::ID_UP;
  321. break;
  322. }
  323. return $id_ob->idArithmetic($action, $options);
  324. }
  325. /**
  326. * @deprecated Use Horde_Mime_Id instead.
  327. */
  328. public static function isChild($base, $id)
  329. {
  330. $id_ob = new Horde_Mime_Id($base);
  331. return $id_ob->isChild($id);
  332. }
  333. /**
  334. * @deprecated Use Horde_Mime_QuotedPrintable instead.
  335. */
  336. public static function quotedPrintableEncode($text, $eol = self::EOL,
  337. $wrap = 76)
  338. {
  339. return Horde_Mime_QuotedPrintable::encode($text, $eol, $wrap);
  340. }
  341. }