Header.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. <?php
  2. // +-----------------------------------------------------------------------+
  3. // | |
  4. // | Copyright © 2003 Heino H. Gehlsen. All Rights Reserved. |
  5. // | http://www.heino.gehlsen.dk/software/license |
  6. // | |
  7. // +-----------------------------------------------------------------------+
  8. // | |
  9. // | This work (including software, documents, or other related items) is |
  10. // | being provided by the copyright holders under the following license. |
  11. // | By obtaining, using and/or copying this work, you (the licensee) |
  12. // | agree that you have read, understood, and will comply with the |
  13. // | following terms and conditions: |
  14. // | |
  15. // | Permission to use, copy, modify, and distribute this software and |
  16. // | its documentation, with or without modification, for any purpose and |
  17. // | without fee or royalty is hereby granted, provided that you include |
  18. // | the following on ALL copies of the software and documentation or |
  19. // | portions thereof, including modifications, that you make: |
  20. // | |
  21. // | 1. The full text of this NOTICE in a location viewable to users of |
  22. // | the redistributed or derivative work. |
  23. // | |
  24. // | 2. Any pre-existing intellectual property disclaimers, notices, or |
  25. // | terms and conditions. If none exist, a short notice of the |
  26. // | following form (hypertext is preferred, text is permitted) should |
  27. // | be used within the body of any redistributed or derivative code: |
  28. // | "Copyright © 2003 Heino H. Gehlsen. All Rights Reserved. |
  29. // | http://www.heino.gehlsen.dk/software/license" |
  30. // | |
  31. // | 3. Notice of any changes or modifications to the files, including |
  32. // | the date changes were made. (We recommend you provide URIs to |
  33. // | the location from which the code is derived.) |
  34. // | |
  35. // | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT |
  36. // | HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, |
  37. // | INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR |
  38. // | FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE |
  39. // | OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, |
  40. // | COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. |
  41. // | |
  42. // | COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, |
  43. // | SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE |
  44. // | SOFTWARE OR DOCUMENTATION. |
  45. // | |
  46. // | The name and trademarks of copyright holders may NOT be used in |
  47. // | advertising or publicity pertaining to the software without specific, |
  48. // | written prior permission. Title to copyright in this software and any |
  49. // | associated documentation will at all times remain with copyright |
  50. // | holders. |
  51. // | |
  52. // +-----------------------------------------------------------------------+
  53. // | |
  54. // | This license is based on the "W3C® SOFTWARE NOTICE AND LICENSE". |
  55. // | No changes have been made to the "W3C® SOFTWARE NOTICE AND LICENSE", |
  56. // | except for the references to the copyright holder, which has either |
  57. // | been changes or removed. |
  58. // | |
  59. // +-----------------------------------------------------------------------+
  60. // $Id: Header.php,v 1.10 2003/10/10 15:35:22 heino Exp $
  61. require_once 'PEAR.php';
  62. define('NET_NNTP_HEADER_SET_UNFOLD', 1);
  63. define('NET_NNTP_HEADER_SET_DECODE', 2);
  64. define('NET_NNTP_HEADER_SET_CLEAN', 4);
  65. define('NET_NNTP_HEADER_SET_KEEPCASE', 8);
  66. define('NET_NNTP_HEADER_SET_DEFAULT', NET_NNTP_HEADER_SET_CLEAN | NET_NNTP_HEADER_SET_UNFOLD | NET_NNTP_HEADER_SET_DECODE);
  67. define('NET_NNTP_HEADER_GET_FOLD', 1);
  68. define('NET_NNTP_HEADER_GET_ENCODE', 2);
  69. define('NET_NNTP_HEADER_GET_DEFAULT', NET_NNTP_HEADER_GET_ENCODE | NET_NNTP_HEADER_GET_FOLD);
  70. /**
  71. * The Net_NNTP_Header class
  72. *
  73. * @version $Revision: 1.10 $
  74. * @package Net_NNTP
  75. *
  76. * @author Heino H. Gehlsen <heino@gehlsen.dk>
  77. */
  78. class Net_NNTP_Header
  79. {
  80. // {{{ properties
  81. /**
  82. * Container for the header fields
  83. *
  84. * @var array
  85. * @access public
  86. */
  87. var $fields;
  88. // }}}
  89. // {{{ constructor
  90. /**
  91. * Constructor
  92. *
  93. * @access public
  94. * @since 0.1
  95. */
  96. function Net_NNTP_Header()
  97. {
  98. // Reset object
  99. $this->reset();
  100. }
  101. // }}}
  102. // {{{ reset()
  103. /**
  104. * Reset the field container
  105. *
  106. * @access public
  107. * @since 0.1
  108. */
  109. function reset()
  110. {
  111. $this->fields = array();
  112. }
  113. // }}}
  114. // {{{ create()
  115. /**
  116. * Create a new instance of Net_NNTP_Header
  117. *
  118. * @param optional mixed $input Can be any of the following:
  119. * (string) RFC2822 style header lines (CRLF included)
  120. * (array) RFC2822 style header lines (CRLF not included)
  121. * (object) Net_NNTP_Header object
  122. * (object) Net_NNTP_Message object
  123. *
  124. * @return object Net_NNTP_Header object
  125. * @access public
  126. * @since 0.1
  127. */
  128. function & create($input = null)
  129. {
  130. switch (true) {
  131. // Null
  132. case is_null($input);
  133. $Object = new Net_NNTP_Header();
  134. return $Object;
  135. break;
  136. // Object
  137. case is_object($input);
  138. switch (true) {
  139. // Header
  140. case is_a($input, 'net_nntp_header'):
  141. return $input;
  142. break;
  143. // Message
  144. case is_a($input, 'net_nntp_message'):
  145. return $input->getHeader();
  146. break;
  147. // Unknown object/class
  148. default:
  149. return PEAR::throwError('Unsupported object/class: '.get_class($input), null);
  150. }
  151. break;
  152. // String & Array
  153. case is_string($input);
  154. case is_array($input);
  155. $Object = new Net_NNTP_Header();
  156. $R = $Object->setFields($input);
  157. if (PEAR::isError($R)) {
  158. return $R;
  159. }
  160. return $Object;
  161. break;
  162. // Unknown type
  163. default:
  164. return PEAR::throwError('Unsupported object/class: '.get_class($input), null);
  165. }
  166. }
  167. // }}}
  168. // {{{ add()
  169. /**
  170. * Add a new field
  171. *
  172. * @param string $tag
  173. * @param string $value
  174. * @param optional int $index
  175. *
  176. * @access public
  177. * @since 0.1
  178. */
  179. function add($tag, $value, $index = null)
  180. {
  181. // Add header to $return array
  182. if (isset($this->fields[$tag]) && is_array($this->fields[$tag])) {
  183. // The header name has already been used at least two times.
  184. $this->fields[$tag][] = $value;
  185. } elseif (isset($this->fields[$tag])) {
  186. // The header name has already been used one time -> change to nedted values.
  187. $this->fields[$tag] = array($this->fields[$tag], $value);
  188. } else {
  189. // The header name has not used until now.
  190. $this->fields[$tag] = $value;
  191. }
  192. }
  193. // }}}
  194. // {{{ replace()
  195. /**
  196. * Replace a field's value
  197. *
  198. * @param string $tag
  199. * @param string $value
  200. * @param optional int $index
  201. *
  202. * @access public
  203. * @since 0.1
  204. */
  205. function replace($tag, $value, $index = null)
  206. {
  207. if (isset($this->fields[$tag])) {
  208. if ($index === null) {
  209. $this->fields[$tag] = $value;
  210. } else {
  211. if (is_array($this->fields[$tag])) {
  212. $this->fields[$tag][$index] = $value;
  213. } else {
  214. //TODO: Currently ignores $index, and just replaces the value
  215. $this->fields[$tag] = $value;
  216. }
  217. }
  218. } else {
  219. $this->fields[$tag] = $value;
  220. }
  221. }
  222. // }}}
  223. // {{{ delete()
  224. /**
  225. * Delete a field
  226. *
  227. * @param string $tag
  228. * @param optional int $index
  229. *
  230. * @access public
  231. * @since 0.1
  232. */
  233. function delete($tag, $index = null)
  234. {
  235. if (isset($this->fields[$tag])) {
  236. if ($index == null) {
  237. unset($this->fields[$tag]);
  238. } else {
  239. if (is_array($this->fields[$tag])) {
  240. unset($this->fields[$tag][$index]);
  241. } else {
  242. unset($this->fields[$tag]);
  243. }
  244. }
  245. } else {
  246. // Do nothing...
  247. }
  248. }
  249. // }}}
  250. // {{{ get()
  251. /**
  252. * Gets the value of a header field
  253. *
  254. * @param string $tag
  255. * @param optional int $index (defaults to 0)
  256. *
  257. * @return string
  258. * @access public
  259. * @since 0.1
  260. */
  261. function get($tag, $index = 0)
  262. {
  263. if (!isset($this->fields[$tag])) {
  264. return null;
  265. }
  266. if (is_array($this->fields[$tag])) {
  267. return $this->fields[$tag][$index];
  268. } else {
  269. if ($index == 0) {
  270. return $this->fields[$tag];
  271. } else {
  272. return null;
  273. }
  274. }
  275. }
  276. // }}}
  277. // {{{ getAll()
  278. /**
  279. * Gets the values of a all occurences of a field
  280. *
  281. * @param string $tag
  282. * @param optional int $index
  283. *
  284. * @return array
  285. * @access public
  286. * @since 0.1
  287. */
  288. function getAll($tag)
  289. {
  290. if (!isset($this->fields[$tag])) {
  291. array();
  292. }
  293. if (is_array($this->fields[$tag])) {
  294. return $this->fields[$tag];
  295. } else {
  296. // TODO: What to do, when not array but index is set...
  297. return array($this->fields[$tag]);
  298. }
  299. }
  300. // }}}
  301. // {{{ count()
  302. /**
  303. * Returns the number of times the given field tag appears in the header.
  304. *
  305. * @param string $tag
  306. *
  307. * @return int
  308. * @access public
  309. * @since 0.1
  310. */
  311. function count($tag)
  312. {
  313. if (!isset($this->fields[$tag])) {
  314. return 0;
  315. }
  316. if (is_array($this->fields[$tag])) {
  317. return count($this->fields[$tag]);
  318. } else {
  319. return 1;
  320. }
  321. }
  322. // }}}
  323. // {{{ tags()
  324. /**
  325. * Returns an array of all the tags that exist in the header.
  326. * Each tag will only appear in the list once.
  327. *
  328. * @return array
  329. * @access public
  330. * @since 0.1
  331. */
  332. function tags()
  333. {
  334. return array_keys($this->fields);
  335. }
  336. // }}}
  337. // {{{ clean()
  338. /**
  339. * Remove any header field that only contains whitespace.
  340. *
  341. * @access public
  342. * @since 0.1
  343. */
  344. function clean()
  345. {
  346. foreach (array_keys($this->fields) as $tag) {
  347. if (is_array($this->fields[$tag])) {
  348. foreach (array_keys($this->fields[$tag]) as $i) {
  349. if (trim($this->fields[$tag][$i]) == '') {
  350. unset($this->fields[$tag][$i]);
  351. }
  352. }
  353. } else {
  354. if (trim($this->fields[$tag]) == '') {
  355. unset($this->fields[$tag]);
  356. }
  357. }
  358. }
  359. }
  360. // }}}
  361. // {{{ setFields()
  362. /**
  363. * Import RFC2822 style header lines given in $input into the object
  364. *
  365. * @param mixed $input Can be any of the following:
  366. * (string) RFC2822 style header lines (CRLF included)
  367. * (array) RFC2822 style header lines (CRLF not included)
  368. * (object) Net_NNTP_Header object
  369. * (object) Net_NNTP_Message object
  370. * @param optional $flags
  371. *
  372. * @access public
  373. * @since 0.1
  374. */
  375. function setFields(&$input, $flags = NET_NNTP_HEADER_SET_DEFAULT)
  376. {
  377. switch (true) {
  378. // Object
  379. case is_object($input):
  380. switch (true) {
  381. case is_a($input, 'net_nntp_header'):
  382. $this =& $input;
  383. break;
  384. case is_a($input, 'net_nntp_message'):
  385. $this =& $input->getHeader();
  386. break;
  387. // Unknown type
  388. default:
  389. return PEAR::throwError('Unsupported object/class: '.get_class($input), null);
  390. }
  391. break;
  392. // String
  393. case is_string($input):
  394. $this->fields = $this->_parseString($input, $flags);
  395. break;
  396. // Array
  397. case is_array($input):
  398. $this->fields = $this->_parseArray($input, $flags);
  399. break;
  400. // Unknown type
  401. default:
  402. return PEAR::throwError('Unsupported type: '.gettype($input), null);
  403. }
  404. }
  405. // }}}
  406. // {{{ getFields()
  407. /**
  408. * Get the array of header fields.
  409. *
  410. * @param optional $flags
  411. *
  412. * @return array
  413. * @access public
  414. * @since 0.1
  415. */
  416. function getFields()
  417. {
  418. return $this->fields;
  419. }
  420. // }}}
  421. // {{{ getFieldsString()
  422. /**
  423. * Export a string of RFC2822 style header style lines from the object.
  424. *
  425. * @param optional $flags
  426. *
  427. * @return string RFC2822 style header lines (CRLF included)
  428. * @access public
  429. * @since 0.1
  430. */
  431. function getFieldsString($flags = NET_NNTP_HEADER_GET_DEFAULT)
  432. {
  433. return $this->_regenerateString($this->fields, $flags);
  434. }
  435. // }}}
  436. // {{{ getFieldsArray()
  437. /**
  438. * Export an array of RFC2822 style header style lines from the object.
  439. *
  440. * @param optional $flags
  441. *
  442. * @return array RFC2822 style header lines (CRLF not included)
  443. * @access public
  444. * @since 0.1
  445. */
  446. function getFieldsArray($flags = NET_NNTP_HEADER_GET_DEFAULT)
  447. {
  448. return $this->_regenerateArray($this->fields, $flags);
  449. }
  450. // }}}
  451. // {{{ _parseString()
  452. /**
  453. * Parse a string of RFC2822 style header lines into a 'header array' with the header names as keys.
  454. *
  455. * @param string $string RFC2822 style header lines (CRLF included)
  456. * @param optional $flags
  457. *
  458. * @return array 'header array' with the header names as keys, values may be nested.
  459. * @access private
  460. * @since 0.1
  461. */
  462. function _parseString($string, $flags)
  463. {
  464. // Clean the header lines
  465. if (($flags & NET_NNTP_HEADER_SET_CLEAN) == NET_NNTP_HEADER_SET_CLEAN) {
  466. $string = $this->cleanString($string);
  467. }
  468. // Unfold the header lines
  469. if (($flags & NET_NNTP_HEADER_SET_UNFOLD) == NET_NNTP_HEADER_SET_UNFOLD) {
  470. $string = $this->unfoldString($string);
  471. }
  472. // Convert to array
  473. $array = explode("\r\n", $string);
  474. // Remove body if present
  475. $i = array_search('', $array);
  476. if ($i != null) {
  477. array_splice($array, $i, (count($array))-$i);
  478. }
  479. // Forward to _parse()
  480. return $this->_parse($array, $flags);
  481. }
  482. // }}}
  483. // {{{ _parseArray()
  484. /**
  485. * Parse an array of RFC2822 style header lines into a 'header array' with the header names as keys.
  486. *
  487. * @param array $array RFC2822 style header lines (CRLF not included)
  488. * @param optional $flags
  489. *
  490. * @return array 'header array' with the header names as keys, values may be nested.
  491. * @access private
  492. * @since 0.1
  493. */
  494. function _parseArray($array, $flags)
  495. {
  496. // Clean the header lines
  497. if (($flags & NET_NNTP_HEADER_SET_CLEAN) == NET_NNTP_HEADER_SET_CLEAN) {
  498. $array = $this->cleanArray($array);
  499. }
  500. // Unfold the header lines
  501. if (($flags & NET_NNTP_HEADER_SET_UNFOLD) == NET_NNTP_HEADER_SET_UNFOLD) {
  502. $array = $this->unfoldArray($array);
  503. }
  504. // Remove body if present
  505. $i = array_search('', $array);
  506. if ($i != null) {
  507. array_splice($array, $i, count($array)-$i);
  508. }
  509. // Forward to _parse()
  510. return $this->_parse($array, $flags);
  511. }
  512. // }}}
  513. // {{{ _parse()
  514. /**
  515. * Parse a cleaned and unfolded array of RFC2822 style header lines into a 'header array' with the header names as keys.
  516. * When header names a'pear more the once, the resulting array will have the values nested in the order of a'pear'ence.
  517. *
  518. * @param array $array RFC2822 style header lines (CRLF not included)
  519. * @param optional $flags
  520. *
  521. * @return array 'header array' with the header names as keys, values may be nested.
  522. * @access private
  523. * @since 0.1
  524. */
  525. function _parse($array, $flags)
  526. {
  527. // Init return variable
  528. $return = array();
  529. // Loop through all headers
  530. foreach ($array as $field) {
  531. // Separate header name and value
  532. if (!preg_match('/([\S]+)\:\s*(.*)\s*/', $field, $matches)) {
  533. // Fail...
  534. }
  535. $name = $matches[1];
  536. $value = $matches[2];
  537. unset($matches);
  538. // Change header name to lower case
  539. if (($flags & NET_NNTP_HEADER_SET_KEEPCASE) != NET_NNTP_HEADER_SET_KEEPCASE) {
  540. $name = strtolower($name);
  541. }
  542. // Decode header value acording to RFC 2047
  543. if (($flags & NET_NNTP_HEADER_SET_DECODE) == NET_NNTP_HEADER_SET_UNFOLD) {
  544. $value = $this->decodeString($value);
  545. }
  546. // Add header to $return array
  547. if (isset($return[$name]) AND is_array($return[$name])) {
  548. // The header name has already been used at least two times.
  549. $return[$name][] = $value;
  550. } elseif (isset($return[$name])) {
  551. // The header name has already been used one time -> change to nedted values.
  552. $return[$name] = array($return[$name], $value);
  553. } else {
  554. // The header name has not used until now.
  555. $return[$name] = $value;
  556. }
  557. }
  558. return $return;
  559. }
  560. // }}}
  561. // {{{ _regenerateString()
  562. /**
  563. * Generate a string of RFC2822 style header lines from the 'header array' given in $array.
  564. *
  565. * @param array $array RFC2822 style header lines
  566. * @param optional $flags
  567. *
  568. * @return string RFC822 style header lines (CRLF included).
  569. * @access private
  570. * @since 0.1
  571. */
  572. function _regenerateString($array, $flags)
  573. {
  574. // ( Forward to _regenerateArray() and then convert to string )
  575. return implode("\r\n", $this->_regenerateArray($array, $flags));
  576. }
  577. // }}}
  578. // {{{ _regenerateArray()
  579. /**
  580. * Generate an array of RFC2822 style header lines from the array given in $array.
  581. *
  582. * @param array $array 'header field array'
  583. * @param optional $flags
  584. *
  585. * @return array RFC822 style header lines (CRLF not included).
  586. * @access private
  587. * @since 0.1
  588. */
  589. function _regenerateArray($array, $flags)
  590. {
  591. // Init return variable
  592. $return = array();
  593. // Loop through headers
  594. foreach ($array as $name => $value) {
  595. // Encode header values acording to RFC 2047
  596. if (($flags & NET_NNTP_HEADER_GET_ENCODE) == NET_NNTP_HEADER_GET_ENCODE) {
  597. if (is_array($value)) {
  598. foreach(array_keys($value) as $key) {
  599. $value[$key] = $this->encodeString($value[$key]);
  600. }
  601. } else {
  602. $value = $this->encodeString($value);
  603. }
  604. }
  605. if (is_array($value)) {
  606. foreach ($value as $sub_value) {
  607. $return[] = $name.': '.$sub_value;
  608. }
  609. } else {
  610. $return[] = $name.': '.$value;
  611. }
  612. }
  613. // Fold headers
  614. if (($flags & NET_NNTP_HEADER_GET_FOLD) == NET_NNTP_HEADER_GET_FOLD) {
  615. $return = $this->foldArray($return);
  616. }
  617. return $return;
  618. }
  619. // }}}
  620. // {{{ unfoldString()
  621. /**
  622. * Do the (RFC822 3.1.1) header unfolding to a string of RFC2822 header lines.
  623. *
  624. * @param string $string RFC2822 header lines to unfolded (CRLF included)
  625. *
  626. * @return string Unfolded RFC2822 header lines (CRLF included)
  627. * @access public
  628. * @since 0.1
  629. */
  630. function unfoldString($string)
  631. {
  632. // Correct \r to \r\n
  633. $string = preg_replace("/\r?\n/", "\r\n", $string);
  634. // Unfold multiline headers
  635. $string = preg_replace("/\r\n(\t| )+/", ' ', $string);
  636. return $string;
  637. }
  638. // }}}
  639. // {{{ unfoldArray()
  640. /**
  641. * Do the (RFC822 3.1.1) header unfolding to an array of RFC2822 header lines.
  642. *
  643. * @param array $array RFC2822 header lines to unfolded (CRLF not included)
  644. *
  645. * @return array Unfolded RFC2822 header lines (CRLF not included)
  646. * @access public
  647. * @since 0.1
  648. */
  649. function unfoldArray($array)
  650. {
  651. // Unfold multiline headers
  652. for ($i = count($array)-1; $i>0; $i--) {
  653. // Check for leading whitespace
  654. if (preg_match('/^(\x09|\x20)/', $array[$i])) {
  655. // Remove folding \r\n
  656. if (substr($array[$i-1], -2) == "\r\n") {
  657. $array[$i-1] = substr($array[$i-1], 0, -2);
  658. }
  659. // Append folded line to prev line
  660. $array[$i-1] = $array[$i-1].' '.ltrim($array[$i], " \t");
  661. // Remove folded line
  662. array_splice($array, $i, 1);
  663. }
  664. }
  665. return $array;
  666. }
  667. // }}}
  668. // {{{ foldArray()
  669. /**
  670. * Folds an array of RFC2822 style header lines.
  671. *
  672. * @param array $array
  673. * @param optional int $maxlen
  674. *
  675. * @return array
  676. * @access public
  677. * @since 0.1
  678. */
  679. function foldArray($array, $maxlen = 78)
  680. {
  681. $return = array();
  682. foreach (array_keys($array) as $key) {
  683. $tmp = $this->_foldExplode($array[$key], $maxlen);
  684. $prepend = '';
  685. foreach (array_keys($tmp) as $key2) {
  686. $return[] = $prepend.$tmp[$key2];
  687. $prepend = "\t";
  688. }
  689. }
  690. return $return;
  691. }
  692. // }}}
  693. // {{{ foldString()
  694. /**
  695. * Folds a string by inserting CRLF's and TAB's where allowed
  696. *
  697. * @param string $string
  698. * @param optional int $maxlen
  699. *
  700. * @return string
  701. * @access public
  702. * @since 0.1
  703. */
  704. function foldString($string, $maxlen = 78)
  705. {
  706. $array = $this->_foldExplode($string, $maxlen);
  707. $return = implode("\r\n\t", $array);
  708. return $return;
  709. }
  710. // }}}
  711. // {{{ _foldExplode()
  712. /**
  713. * Unfold $string, and return a 'folded' array
  714. *
  715. * The current implementation is still experimental, and is NOT expected to comply with RFC2822 !!!
  716. *
  717. * @param string $string
  718. * @param optional int $maxlen
  719. *
  720. * @return array
  721. * @access private
  722. * @since 0.1
  723. */
  724. function _foldExplode($string, $maxlen = 78)
  725. //TODO:
  726. {
  727. if ($maxlen < 20) {
  728. $maxlen = 20;
  729. }
  730. if ($maxlen > 998) {
  731. $maxlen = 998;
  732. }
  733. if (strlen($string) <= $maxlen) {
  734. return array($string);
  735. }
  736. $min = (int) ($maxlen * (2/5)) - 4;
  737. $max = $maxlen - 5; // 4 for leading spcs + 1 for [\,\;]
  738. // try splitting at ',' or ';' >2/5 along the line
  739. // Split the line up
  740. // next split a whitespace
  741. // else we are looking at a single word and probably don't want to split
  742. $exp = array();
  743. $exp[] = "[^\"]\{$min,$max}?[\,\;]\s";
  744. $exp[] = "[^\"]\{1,$max}\s";
  745. $exp[] = "[^\s\"]*(?:\"[^\"]*\"[^\s\"]*)+\s";
  746. $exp[] = "[^\s\"]+\s";
  747. $exp ="/^\s*(".implode('|', $exp).")(.*)\$/x";
  748. $tmp = $string;
  749. $return = array();
  750. while ((strlen($tmp) > $max) && (preg_match($exp, $tmp, $match))) {
  751. $return[] = $match[1];
  752. $tmp = $match[2];
  753. }
  754. $return[] = $tmp;
  755. return $return;
  756. }
  757. // }}}
  758. // {{{ decodeString()
  759. /**
  760. * Given a header/string, this function will decode it according to RFC2047.
  761. * Probably not *exactly* conformant, but it does pass all the given
  762. * examples (in RFC2047).
  763. *
  764. * @param string $input Input header value to decode
  765. *
  766. * @return string Decoded header value
  767. * @access public
  768. * @since 0.1
  769. */
  770. function decodeString($input)
  771. {
  772. // Remove white space between encoded-words
  773. $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
  774. // For each encoded-word...
  775. while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
  776. $encoded = $matches[1];
  777. $charset = $matches[2];
  778. $encoding = $matches[3];
  779. $text = $matches[4];
  780. switch (strtolower($encoding)) {
  781. case 'b': // RFC2047 4.1
  782. $text = base64_decode($text);
  783. break;
  784. case 'q': // RFC2047 4.2
  785. $text = str_replace('_', ' ', $text);
  786. preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  787. foreach($matches[1] as $value)
  788. $text = str_replace('='.$value, chr(hexdec($value)), $text);
  789. break;
  790. }
  791. $input = str_replace($encoded, $text, $header);
  792. }
  793. return $input;
  794. }
  795. // }}}
  796. // {{{ encodeString()
  797. /**
  798. * Encodes the string given in $string as per RFC2047
  799. *
  800. * @param string $string The string to encode
  801. *
  802. * @return string Encoded string
  803. * @access public
  804. * @since 0.1
  805. */
  806. function encodeString($string)
  807. {
  808. // TODO: could be better! (Look into CPAN's Encode::MIME::Header)
  809. $charset = 'iso-8859-1';
  810. preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $string, $matches);
  811. foreach ($matches[1] as $value) {
  812. $replacement = preg_replace('/([\x80-\xFF])/e', '"=" . strtoupper(dechex(ord("\1")))', $value);
  813. $string = str_replace($value, '=?' . $charset . '?Q?' . $replacement . '?=', $string);
  814. }
  815. return $string;
  816. }
  817. // }}}
  818. // {{{ cleanString()
  819. /**
  820. * Removes CRLF and misplaced empty lines before and after actual headerlines.
  821. *
  822. * @param string $string.
  823. *
  824. * @return string
  825. * @access public
  826. * @since 0.1
  827. */
  828. function cleanString($string)
  829. {
  830. // Correct missing CR's before LF's
  831. $string = preg_replace("!\r?\n!", "\r\n", $string);
  832. // Remove empty lines from start and end.
  833. // TODO: This should be done better...
  834. $string = trim($string, "\r\n");
  835. return $string;
  836. }
  837. // }}}
  838. // {{{ cleanArray()
  839. /**
  840. * Removes CRLF and misplaced empty lines before and after actual headerlines.
  841. *
  842. * @param array $input
  843. *
  844. * @return array
  845. * @access public
  846. * @since 0.1
  847. */
  848. function cleanArray($input)
  849. {
  850. // Remove empty lines from the start
  851. while (reset($input) == "\r\n") {
  852. array_shift($input);
  853. }
  854. // Remove empty lines from the end
  855. while (end($input) == "\r\n") {
  856. array_pop($input);
  857. }
  858. // Run backwards through all lines
  859. for ($i = count($input)-1; $i > 0; $i--) {
  860. // Remove \r\n from the end
  861. $input = preg_replace("/\r?\n$/", '', $input);
  862. }
  863. return $input;
  864. }
  865. // }}}
  866. }
  867. ?>