PageRenderTime 37ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Mail/MimePart.php

http://github.com/nette/nette
PHP | 392 lines | 228 code | 71 blank | 93 comment | 24 complexity | 5bf9e22ed9b614653ee71b20a0bc966f MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Mail;
  11. use Nette,
  12. Nette\Utils\Strings;
  13. /**
  14. * MIME message part.
  15. *
  16. * @author David Grudl
  17. *
  18. * @property-read array $headers
  19. * @property-write $contentType
  20. * @property string $encoding
  21. * @property mixed $body
  22. */
  23. class MimePart extends Nette\Object
  24. {
  25. /** encoding */
  26. const ENCODING_BASE64 = 'base64',
  27. ENCODING_7BIT = '7bit',
  28. ENCODING_8BIT = '8bit',
  29. ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  30. /** @internal */
  31. const EOL = "\r\n";
  32. const LINE_LENGTH = 76;
  33. /** @var array */
  34. private $headers = array();
  35. /** @var array */
  36. private $parts = array();
  37. /** @var string */
  38. private $body;
  39. /**
  40. * Sets a header.
  41. * @param string
  42. * @param string|array value or pair email => name
  43. * @param bool
  44. * @return MimePart provides a fluent interface
  45. */
  46. public function setHeader($name, $value, $append = FALSE)
  47. {
  48. if (!$name || preg_match('#[^a-z0-9-]#i', $name)) {
  49. throw new Nette\InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
  50. }
  51. if ($value == NULL) { // intentionally ==
  52. if (!$append) {
  53. unset($this->headers[$name]);
  54. }
  55. } elseif (is_array($value)) { // email
  56. $tmp = & $this->headers[$name];
  57. if (!$append || !is_array($tmp)) {
  58. $tmp = array();
  59. }
  60. foreach ($value as $email => $recipient) {
  61. if ($recipient !== NULL && !Strings::checkEncoding($recipient)) {
  62. Nette\Utils\Validators::assert($recipient, 'unicode', "header '$name'");
  63. }
  64. if (preg_match('#[\r\n]#', $recipient)) {
  65. throw new Nette\InvalidArgumentException("Name must not contain line separator.");
  66. }
  67. Nette\Utils\Validators::assert($email, 'email', "header '$name'");
  68. $tmp[$email] = $recipient;
  69. }
  70. } else {
  71. $value = (string) $value;
  72. if (!Strings::checkEncoding($value)) {
  73. throw new Nette\InvalidArgumentException("Header is not valid UTF-8 string.");
  74. }
  75. $this->headers[$name] = preg_replace('#[\r\n]+#', ' ', $value);
  76. }
  77. return $this;
  78. }
  79. /**
  80. * Returns a header.
  81. * @param string
  82. * @return mixed
  83. */
  84. public function getHeader($name)
  85. {
  86. return isset($this->headers[$name]) ? $this->headers[$name] : NULL;
  87. }
  88. /**
  89. * Removes a header.
  90. * @param string
  91. * @return MimePart provides a fluent interface
  92. */
  93. public function clearHeader($name)
  94. {
  95. unset($this->headers[$name]);
  96. return $this;
  97. }
  98. /**
  99. * Returns an encoded header.
  100. * @param string
  101. * @param string
  102. * @return string
  103. */
  104. public function getEncodedHeader($name)
  105. {
  106. $offset = strlen($name) + 2; // colon + space
  107. if (!isset($this->headers[$name])) {
  108. return NULL;
  109. } elseif (is_array($this->headers[$name])) {
  110. $s = '';
  111. foreach ($this->headers[$name] as $email => $name) {
  112. if ($name != NULL) { // intentionally ==
  113. $s .= self::encodeHeader(
  114. strpbrk($name, '.,;<@>()[]"=?') ? '"' . addcslashes($name, '"\\') . '"' : $name,
  115. $offset
  116. );
  117. $email = " <$email>";
  118. }
  119. $email .= ',';
  120. if ($s !== '' && $offset + strlen($email) > self::LINE_LENGTH) {
  121. $s .= self::EOL . "\t";
  122. $offset = 1;
  123. }
  124. $s .= $email;
  125. $offset += strlen($email);
  126. }
  127. return substr($s, 0, -1); // last comma
  128. } elseif (preg_match('#^(\S+; (?:file)?name=)"(.*)"$#', $this->headers[$name], $m)) { // Content-Disposition
  129. $offset += strlen($m[1]);
  130. return $m[1] . '"' . self::encodeHeader($m[2], $offset) . '"';
  131. } else {
  132. return self::encodeHeader($this->headers[$name], $offset);
  133. }
  134. }
  135. /**
  136. * Returns all headers.
  137. * @return array
  138. */
  139. public function getHeaders()
  140. {
  141. return $this->headers;
  142. }
  143. /**
  144. * Sets Content-Type header.
  145. * @param string
  146. * @param string
  147. * @return MimePart provides a fluent interface
  148. */
  149. public function setContentType($contentType, $charset = NULL)
  150. {
  151. $this->setHeader('Content-Type', $contentType . ($charset ? "; charset=$charset" : ''));
  152. return $this;
  153. }
  154. /**
  155. * Sets Content-Transfer-Encoding header.
  156. * @param string
  157. * @return MimePart provides a fluent interface
  158. */
  159. public function setEncoding($encoding)
  160. {
  161. $this->setHeader('Content-Transfer-Encoding', $encoding);
  162. return $this;
  163. }
  164. /**
  165. * Returns Content-Transfer-Encoding header.
  166. * @return string
  167. */
  168. public function getEncoding()
  169. {
  170. return $this->getHeader('Content-Transfer-Encoding');
  171. }
  172. /**
  173. * Adds or creates new multipart.
  174. * @param MimePart
  175. * @return MimePart
  176. */
  177. public function addPart(MimePart $part = NULL)
  178. {
  179. return $this->parts[] = $part === NULL ? new self : $part;
  180. }
  181. /**
  182. * Sets textual body.
  183. * @param mixed
  184. * @return MimePart provides a fluent interface
  185. */
  186. public function setBody($body)
  187. {
  188. $this->body = $body;
  189. return $this;
  190. }
  191. /**
  192. * Gets textual body.
  193. * @return mixed
  194. */
  195. public function getBody()
  196. {
  197. return $this->body;
  198. }
  199. /********************* building ****************d*g**/
  200. /**
  201. * Returns encoded message.
  202. * @return string
  203. */
  204. public function generateMessage()
  205. {
  206. $output = '';
  207. $boundary = '--------' . Strings::random();
  208. foreach ($this->headers as $name => $value) {
  209. $output .= $name . ': ' . $this->getEncodedHeader($name);
  210. if ($this->parts && $name === 'Content-Type') {
  211. $output .= ';' . self::EOL . "\tboundary=\"$boundary\"";
  212. }
  213. $output .= self::EOL;
  214. }
  215. $output .= self::EOL;
  216. $body = (string) $this->body;
  217. if ($body !== '') {
  218. switch ($this->getEncoding()) {
  219. case self::ENCODING_QUOTED_PRINTABLE:
  220. $output .= function_exists('quoted_printable_encode') ? quoted_printable_encode($body) : self::encodeQuotedPrintable($body);
  221. break;
  222. case self::ENCODING_BASE64:
  223. $output .= rtrim(chunk_split(base64_encode($body), self::LINE_LENGTH, self::EOL));
  224. break;
  225. case self::ENCODING_7BIT:
  226. $body = preg_replace('#[\x80-\xFF]+#', '', $body);
  227. // break intentionally omitted
  228. case self::ENCODING_8BIT:
  229. $body = str_replace(array("\x00", "\r"), '', $body);
  230. $body = str_replace("\n", self::EOL, $body);
  231. $output .= $body;
  232. break;
  233. default:
  234. throw new Nette\InvalidStateException('Unknown encoding.');
  235. }
  236. }
  237. if ($this->parts) {
  238. if (substr($output, -strlen(self::EOL)) !== self::EOL) {
  239. $output .= self::EOL;
  240. }
  241. foreach ($this->parts as $part) {
  242. $output .= '--' . $boundary . self::EOL . $part->generateMessage() . self::EOL;
  243. }
  244. $output .= '--' . $boundary.'--';
  245. }
  246. return $output;
  247. }
  248. /********************* QuotedPrintable helpers ****************d*g**/
  249. /**
  250. * Converts a 8 bit header to a quoted-printable string.
  251. * @param string
  252. * @param string
  253. * @param int
  254. * @return string
  255. */
  256. private static function encodeHeader($s, & $offset = 0)
  257. {
  258. $o = '';
  259. if ($offset >= 55) { // maximum for iconv_mime_encode
  260. $o = self::EOL . "\t";
  261. $offset = 1;
  262. }
  263. if (strspn($s, "!\"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`abcdefghijklmnopqrstuvwxyz{|}=? _\r\n\t") === strlen($s)
  264. && ($offset + strlen($s) <= self::LINE_LENGTH)
  265. ) {
  266. $offset += strlen($s);
  267. return $o . $s;
  268. }
  269. $o .= str_replace("\n ", "\n\t", substr(iconv_mime_encode(str_repeat(' ', $offset), $s, array(
  270. 'scheme' => 'B', // Q is broken
  271. 'input-charset' => 'UTF-8',
  272. 'output-charset' => 'UTF-8',
  273. )), $offset + 2));
  274. $offset = strlen($o) - strrpos($o, "\n");
  275. return $o;
  276. }
  277. /**
  278. * Converts a 8 bit string to a quoted-printable string.
  279. * @param string
  280. * @return string
  281. *//*5.2*
  282. public static function encodeQuotedPrintable($s)
  283. {
  284. $range = '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}'; // \x21-\x7E without \x3D
  285. $pos = 0;
  286. $len = 0;
  287. $o = '';
  288. $size = strlen($s);
  289. while ($pos < $size) {
  290. if ($l = strspn($s, $range, $pos)) {
  291. while ($len + $l > self::LINE_LENGTH - 1) { // 1 = length of suffix =
  292. $lx = self::LINE_LENGTH - $len - 1;
  293. $o .= substr($s, $pos, $lx) . '=' . self::EOL;
  294. $pos += $lx;
  295. $l -= $lx;
  296. $len = 0;
  297. }
  298. $o .= substr($s, $pos, $l);
  299. $len += $l;
  300. $pos += $l;
  301. } else {
  302. $len += 3;
  303. if ($len > self::LINE_LENGTH - 1) {
  304. $o .= '=' . self::EOL;
  305. $len = 3;
  306. }
  307. $o .= '=' . strtoupper(bin2hex($s[$pos]));
  308. $pos++;
  309. }
  310. }
  311. return rtrim($o, '=' . self::EOL);
  312. }*/
  313. }