Source for file MailMimePart.php

Documentation is available at MailMimePart.php

  1. 1: <?php
  2. 2:  
  3. 3: /**
  4. 4:  * Nette Framework
  5. 5:  *
  6. 6:  * @copyright  Copyright (c) 2004, 2010 David Grudl
  7. 7:  * @license    http://nettephp.com/license  Nette license
  8. 8:  * @link       http://nettephp.com
  9. 9:  * @category   Nette
  10. 10:  * @package    Nette\Mail
  11. 11:  */
  12. 12:  
  13. 13:  
  14. 14:  
  15. 15: /**
  16. 16:  * MIME message part.
  17. 17:  *
  18. 18:  * @copyright  Copyright (c) 2004, 2010 David Grudl
  19. 19:  * @package    Nette\Mail
  20. 20:  *
  21. 21:  * @property   string $encoding 
  22. 22:  * @property   string $body 
  23. 23:  * @property-read array $headers 
  24. 24:  */
  25. 25: class MailMimePart extends Object
  26. 26: {
  27. 27:     /**#@+ encoding */
  28. 28:     const ENCODING_BASE64 = 'base64';
  29. 29:     const ENCODING_7BIT = '7bit';
  30. 30:     const ENCODING_8BIT = '8bit';
  31. 31:     const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  32. 32:     /**#@-*/
  33. 33:  
  34. 34:     /**#@+ @ignore internal */
  35. 35:     const EOL "\r\n";
  36. 36:     const LINE_LENGTH 76;
  37. 37:     /**#@-*/
  38. 38:  
  39. 39:     /** @var array */
  40. 40:     private $headers array();
  41. 41:  
  42. 42:     /** @var array */
  43. 43:     private $parts array();
  44. 44:  
  45. 45:     /** @var string */
  46. 46:     private $body;
  47. 47:  
  48. 48:  
  49. 49:  
  50. 50:     /**
  51. 51:      * Sets a header.
  52. 52:      * @param  string 
  53. 53:      * @param  string|array value or pair email => name
  54. 54:      * @param  bool 
  55. 55:      * @return MailMimePart  provides a fluent interface
  56. 56:      */
  57. 57:     public function setHeader($name$value$append FALSE)
  58. 58:     {
  59. 59:         if (!$name || preg_match('#[^a-z0-9-]#i'$name)) {
  60. 60:             throw new InvalidArgumentException("Header name must be non-empty alphanumeric string, '$name' given.");
  61. 61:         }
  62. 62:  
  63. 63:         if ($value == NULL// intentionally ==
  64. 64:             if (!$append{
  65. 65:                 unset($this->headers[$name]);
  66. 66:             }
  67. 67:  
  68. 68:         elseif (is_array($value)) // email
  69. 69:             $tmp $this->headers[$name];
  70. 70:             if (!$append || !is_array($tmp)) {
  71. 71:                 $tmp array();
  72. 72:             }
  73. 73:  
  74. 74:             foreach ($value as $email => $name{
  75. 75:                 if (!preg_match('#^[^@",\s]+@[^@",\s]+\.[a-z]{2,10}$#i'$email)) {
  76. 76:                     throw new InvalidArgumentException("Email address '$email' is not valid.");
  77. 77:                 }
  78. 78:  
  79. 79:                 if (preg_match('#[\r\n]#'$name)) {
  80. 80:                     throw new InvalidArgumentException("Name cannot contain the line separator.");
  81. 81:                 }
  82. 82:                 $tmp[$email$name;
  83. 83:             }
  84. 84:  
  85. 85:         else {
  86. 86:             $this->headers[$namepreg_replace('#[\r\n]+#'' '$value);
  87. 87:         }
  88. 88:         return $this;
  89. 89:     }
  90. 90:  
  91. 91:  
  92. 92:  
  93. 93:     /**
  94. 94:      * Returns a header.
  95. 95:      * @param  string 
  96. 96:      * @return mixed 
  97. 97:      */
  98. 98:     public function getHeader($name)
  99. 99:     {
  100. 100:         return isset($this->headers[$name]$this->headers[$nameNULL;
  101. 101:     }
  102. 102:  
  103. 103:  
  104. 104:  
  105. 105:     /**
  106. 106:      * Removes a header.
  107. 107:      * @param  string 
  108. 108:      * @return MailMimePart  provides a fluent interface
  109. 109:      */
  110. 110:     public function clearHeader($name)
  111. 111:     {
  112. 112:         unset($this->headers[$name]);
  113. 113:         return $this;
  114. 114:     }
  115. 115:  
  116. 116:  
  117. 117:  
  118. 118:     /**
  119. 119:      * Returns an encoded header.
  120. 120:      * @param  string 
  121. 121:      * @param  string 
  122. 122:      * @return string 
  123. 123:      */
  124. 124:     public function getEncodedHeader($name$charset 'UTF-8')
  125. 125:     {
  126. 126:         $len strlen($name2;
  127. 127:  
  128. 128:         if (!isset($this->headers[$name])) {
  129. 129:             return NULL;
  130. 130:  
  131. 131:         elseif (is_array($this->headers[$name])) {
  132. 132:             $s '';
  133. 133:             foreach ($this->headers[$nameas $email => $name{
  134. 134:                 if ($name != NULL// intentionally ==
  135. 135:                     $s .= self::encodeQuotedPrintableHeader(
  136. 136:                         strspn($name'.,;<@>()[]"=?''"' addcslashes($name'"\\''"' $name,
  137. 137:                         $charset$len
  138. 138:                     );
  139. 139:                     $email " <$email>";
  140. 140:                 }
  141. 141:                 if ($len strlen($emailself::LINE_LENGTH{
  142. 142:                     $s .= self::EOL "\t";
  143. 143:                     $len 1;
  144. 144:                 }
  145. 145:                 $s .= "$email,";
  146. 146:                 $len += strlen($email1;
  147. 147:             }
  148. 148:             return substr($s0-1);
  149. 149:  
  150. 150:         else {
  151. 151:             return self::encodeQuotedPrintableHeader($this->headers[$name]$charset$len);
  152. 152:         }
  153. 153:     }
  154. 154:  
  155. 155:  
  156. 156:  
  157. 157:     /**
  158. 158:      * Returns all headers.
  159. 159:      * @return array 
  160. 160:      */
  161. 161:     public function getHeaders()
  162. 162:     {
  163. 163:         return $this->headers;
  164. 164:     }
  165. 165:  
  166. 166:  
  167. 167:  
  168. 168:     /**
  169. 169:      * Sets Content-Type header.
  170. 170:      * @param  string 
  171. 171:      * @param  string 
  172. 172:      * @return MailMimePart  provides a fluent interface
  173. 173:      */
  174. 174:     public function setContentType($contentType$charset NULL)
  175. 175:     {
  176. 176:         $this->setHeader('Content-Type'$contentType ($charset "; charset=$charset''));
  177. 177:         return $this;
  178. 178:     }
  179. 179:  
  180. 180:  
  181. 181:  
  182. 182:     /**
  183. 183:      * Sets Content-Transfer-Encoding header.
  184. 184:      * @param  string 
  185. 185:      * @return MailMimePart  provides a fluent interface
  186. 186:      */
  187. 187:     public function setEncoding($encoding)
  188. 188:     {
  189. 189:         $this->setHeader('Content-Transfer-Encoding'$encoding);
  190. 190:         return $this;
  191. 191:     }
  192. 192:  
  193. 193:  
  194. 194:  
  195. 195:     /**
  196. 196:      * Returns Content-Transfer-Encoding header.
  197. 197:      * @return string 
  198. 198:      */
  199. 199:     public function getEncoding()
  200. 200:     {
  201. 201:         return $this->getHeader('Content-Transfer-Encoding');
  202. 202:     }
  203. 203:  
  204. 204:  
  205. 205:  
  206. 206:     /**
  207. 207:      * Adds or creates new multipart.
  208. 208:      * @param  MailMimePart 
  209. 209:      * @return MailMimePart 
  210. 210:      */
  211. 211:     public function addPart(MailMimePart $part NULL)
  212. 212:     {
  213. 213:         return $this->parts[$part === NULL new self $part;
  214. 214:     }
  215. 215:  
  216. 216:  
  217. 217:  
  218. 218:     /**
  219. 219:      * Sets textual body.
  220. 220:      * @param  mixed 
  221. 221:      * @return MailMimePart  provides a fluent interface
  222. 222:      */
  223. 223:     public function setBody($body)
  224. 224:     {
  225. 225:         $this->body $body;
  226. 226:         return $this;
  227. 227:     }
  228. 228:  
  229. 229:  
  230. 230:  
  231. 231:     /**
  232. 232:      * Gets textual body.
  233. 233:      * @return mixed 
  234. 234:      */
  235. 235:     public function getBody()
  236. 236:     {
  237. 237:         return $this->body;
  238. 238:     }
  239. 239:  
  240. 240:  
  241. 241:  
  242. 242:     /********************* building ****************d*g**/
  243. 243:  
  244. 244:  
  245. 245:  
  246. 246:     /**
  247. 247:      * Returns encoded message.
  248. 248:      * @return string 
  249. 249:      */
  250. 250:     public function generateMessage()
  251. 251:     {
  252. 252:         $output '';
  253. 253:         $boundary '--------' md5(uniqid(''TRUE));
  254. 254:  
  255. 255:         foreach ($this->headers as $name => $value{
  256. 256:             $output .= $name ': ' $this->getEncodedHeader($name);
  257. 257:             if ($this->parts && $name === 'Content-Type'{
  258. 258:                 $output .= ';' self::EOL "\tboundary=\"$boundary\"";
  259. 259:             }
  260. 260:             $output .= self::EOL;
  261. 261:         }
  262. 262:         $output .= self::EOL;
  263. 263:  
  264. 264:         $body = (string) $this->body;
  265. 265:         if ($body !== ''{
  266. 266:             switch ($this->getEncoding()) {
  267. 267:             case self::ENCODING_QUOTED_PRINTABLE:
  268. 268:                 $output .= function_exists('quoted_printable_encode'quoted_printable_encode($bodyself::encodeQuotedPrintable($body);
  269. 269:                 break;
  270. 270:  
  271. 271:             case self::ENCODING_BASE64:
  272. 272:                 $output .= rtrim(chunk_split(base64_encode($body)self::LINE_LENGTHself::EOL));
  273. 273:                 break;
  274. 274:  
  275. 275:             case self::ENCODING_7BIT:
  276. 276:                 $body preg_replace('#[\x80-\xFF]+#'''$body);
  277. 277:                 // break intentionally omitted
  278. 278:  
  279. 279:             case self::ENCODING_8BIT:
  280. 280:                 $body str_replace(array("\x00""\r")''$body);
  281. 281:                 $body str_replace("\n"self::EOL$body);
  282. 282:                 $output .= $body;
  283. 283:                 break;
  284. 284:  
  285. 285:             default:
  286. 286:                 throw new InvalidStateException('Unknown encoding');
  287. 287:             }
  288. 288:         }
  289. 289:  
  290. 290:         if ($this->parts{
  291. 291:             if (substr($output-strlen(self::EOL)) !== self::EOL$output .= self::EOL;
  292. 292:             foreach ($this->parts as $part{
  293. 293:                 $output .= '--' $boundary self::EOL $part->generateMessage(self::EOL;
  294. 294:             }
  295. 295:             $output .= '--' $boundary.'--';
  296. 296:         }
  297. 297:  
  298. 298:         return $output;
  299. 299:     }
  300. 300:  
  301. 301:  
  302. 302:  
  303. 303:     /********************* QuotedPrintable helpers ****************d*g**/
  304. 304:  
  305. 305:  
  306. 306:  
  307. 307:     /**
  308. 308:      * Converts a 8 bit header to a quoted-printable string.
  309. 309:      * @param  string 
  310. 310:      * @param  string 
  311. 311:      * @param  int 
  312. 312:      * @return string 
  313. 313:      */
  314. 314:     private static function encodeQuotedPrintableHeader($s$charset 'UTF-8'$len 0)
  315. 315:     {
  316. 316:         $range '!"#$%&\'()*+,-./0123456789:;<>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^`abcdefghijklmnopqrstuvwxyz{|}'// \x21-\x7E without \x3D \x3F \x5F
  317. 317:  
  318. 318:         if (strspn($s$range "=? _\r\n\t"=== strlen($s)) {
  319. 319:             return $s;
  320. 320:         }
  321. 321:  
  322. 322:         $prefix "=?$charset?Q?";
  323. 323:         $pos 0;
  324. 324:         $len += strlen($prefix);
  325. 325:         $o $prefix;
  326. 326:         $size strlen($s);
  327. 327:         while ($pos $size{
  328. 328:             if ($l strspn($s$range$pos)) {
  329. 329:                 while ($len $l self::LINE_LENGTH 2// 2 = length of suffix ?=
  330. 330:                     $lx self::LINE_LENGTH $len 2;
  331. 331:                     $o .= substr($s$pos$lx'?=' self::EOL "\t" $prefix;
  332. 332:                     $pos += $lx;
  333. 333:                     $l -= $lx;
  334. 334:                     $len strlen($prefix1;
  335. 335:                 }
  336. 336:                 $o .= substr($s$pos$l);
  337. 337:                 $len += $l;
  338. 338:                 $pos += $l;
  339. 339:  
  340. 340:             else {
  341. 341:                 $len += 3;
  342. 342:                 // \xC0 tests UTF-8 character boudnary; 9 is reserved space for 4bytes UTF-8 character
  343. 343:                 if (($s[$pos"\xC0"!== "\x80" && $len self::LINE_LENGTH 9{
  344. 344:                     $o .= '?=' self::EOL "\t" $prefix;
  345. 345:                     $len strlen($prefix3;
  346. 346:                 }
  347. 347:                 $o .= '=' strtoupper(bin2hex($s[$pos]));
  348. 348:                 $pos++;
  349. 349:             }
  350. 350:         }
  351. 351:         return $o '?=';
  352. 352:     }
  353. 353:  
  354. 354:  
  355. 355:  
  356. 356:     /**
  357. 357:      * Converts a 8 bit string to a quoted-printable string.
  358. 358:      * @param  string 
  359. 359:      * @return string 
  360. 360:      */
  361. 361:     public static function encodeQuotedPrintable($s)
  362. 362:     {
  363. 363:         $range '!"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}'// \x21-\x7E without \x3D
  364. 364:         $pos 0;
  365. 365:         $len 0;
  366. 366:         $o '';
  367. 367:         $size strlen($s);
  368. 368:         while ($pos $size{
  369. 369:             if ($l strspn($s$range$pos)) {
  370. 370:                 while ($len $l self::LINE_LENGTH 1// 1 = length of suffix =
  371. 371:                     $lx self::LINE_LENGTH $len 1;
  372. 372:                     $o .= substr($s$pos$lx'=' self::EOL;
  373. 373:                     $pos += $lx;
  374. 374:                     $l -= $lx;
  375. 375:                     $len 0;
  376. 376:                 }
  377. 377:                 $o .= substr($s$pos$l);
  378. 378:                 $len += $l;
  379. 379:                 $pos += $l;
  380. 380:  
  381. 381:             else {
  382. 382:                 $len += 3;
  383. 383:                 if ($len self::LINE_LENGTH 1{
  384. 384:                     $o .= '=' self::EOL;
  385. 385:                     $len 3;
  386. 386:                 }
  387. 387:                 $o .= '=' strtoupper(bin2hex($s[$pos]));
  388. 388:                 $pos++;
  389. 389:             }
  390. 390:         }
  391. 391:         return rtrim($o'=' self::EOL);
  392. 392:     }
  393. 393: