Overview

Packages

  • Mime

Classes

  • Horde_Mime
  • Horde_Mime_Address
  • Horde_Mime_Exception
  • Horde_Mime_Headers
  • Horde_Mime_Magic
  • Horde_Mime_Mail
  • Horde_Mime_Mdn
  • Horde_Mime_Part
  • Horde_Mime_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * This class contains functions related to handling the headers of MIME data.
  4:  *
  5:  * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (LGPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  9:  *
 10:  * @author   Michael Slusarz <slusarz@horde.org>
 11:  * @category Horde
 12:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 13:  * @package  Mime
 14:  */
 15: class Horde_Mime_Headers implements Serializable
 16: {
 17:     /* Serialized version. */
 18:     const VERSION = 1;
 19: 
 20:     /* Constants for getValue(). */
 21:     const VALUE_STRING = 1;
 22:     const VALUE_BASE = 2;
 23:     const VALUE_PARAMS = 3;
 24: 
 25:     /**
 26:      * The default charset to use when parsing text parts with no charset
 27:      * information.
 28:      *
 29:      * @var string
 30:      */
 31:     static public $defaultCharset = 'us-ascii';
 32: 
 33:     /**
 34:      * The internal headers array.
 35:      *
 36:      * Keys are the lowercase header name.
 37:      * Values are:
 38:      *   - h: The case-sensitive header name.
 39:      *   - p: Parameters for this header.
 40:      *   - v: The value of the header. Values are stored in UTF-8.
 41:      *
 42:      * @var array
 43:      */
 44:     protected $_headers = array();
 45: 
 46:     /**
 47:      * The sequence to use as EOL for the headers.
 48:      * The default is currently to output the EOL sequence internally as
 49:      * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
 50:      * To be RFC complaint, the full <CR><LF> EOL combination should be used
 51:      * when sending a message.
 52:      *
 53:      * @var string
 54:      */
 55:     protected $_eol = "\n";
 56: 
 57:     /**
 58:      * The User-Agent string to use.
 59:      *
 60:      * @var string
 61:      */
 62:     protected $_agent = null;
 63: 
 64:     /**
 65:      * List of single header fields.
 66:      *
 67:      * @var array
 68:      */
 69:     protected $_singleFields = array(
 70:         // Mail: RFC 5322
 71:         'to', 'from', 'cc', 'bcc', 'date', 'sender', 'reply-to',
 72:         'message-id', 'in-reply-to', 'references', 'subject', 'x-priority',
 73:         // MIME: RFC 1864
 74:         'content-md5',
 75:         // MIME: RFC 2045
 76:         'mime-version', 'content-type', 'content-transfer-encoding',
 77:         'content-id', 'content-description',
 78:         // MIME: RFC 2110
 79:         'content-base',
 80:         // MIME: RFC 2183
 81:         'content-disposition',
 82:         // MIME: RFC 2424
 83:         'content-duration',
 84:         // MIME: RFC 2557
 85:         'content-location',
 86:         // MIME: RFC 2912 [3]
 87:         'content-features',
 88:         // MIME: RFC 3282
 89:         'content-language',
 90:         // MIME: RFC 3297
 91:         'content-alternative'
 92:     );
 93: 
 94:     /**
 95:      * Returns the internal header array in array format.
 96:      *
 97:      * @param array $options  Optional parameters:
 98:      *   - canonical: (boolean) Use canonical (RFC 822/2045) line endings?
 99:      *                DEFAULT: Uses $this->_eol
100:      *   - charset: (string) Encodes the headers using this charset. If empty,
101:      *              encodes using internal charset (UTF-8).
102:      *              DEFAULT: No encoding.
103:      *   - defserver: (string) The default domain to append to mailboxes.
104:      *                DEFAULT: No default name.
105:      *   - nowrap: (integer) Don't wrap the headers.
106:      *             DEFAULT: Headers are wrapped.
107:      *
108:      * @return array  The headers in array format.
109:      */
110:     public function toArray(array $options = array())
111:     {
112:         $address_keys = $this->addressFields();
113:         $charset = array_key_exists('charset', $options)
114:             ? (empty($options['charset']) ? 'UTF-8' : $options['charset'])
115:             : null;
116:         $eol = empty($options['canonical'])
117:             ? $this->_eol
118:             : "\r\n";
119:         $mime = $this->mimeParamFields();
120:         $ret = array();
121: 
122:         foreach ($this->_headers as $header => $ob) {
123:             $val = is_array($ob['v']) ? $ob['v'] : array($ob['v']);
124: 
125:             foreach (array_keys($val) as $key) {
126:                 if (in_array($header, $address_keys) ) {
127:                     /* Address encoded headers. */
128:                     try {
129:                         $text = Horde_Mime::encodeAddress(Horde_String::convertCharset($val[$key], 'UTF-8', $charset), $charset, empty($options['defserver']) ? null : $options['defserver']);
130:                     } catch (Horde_Mime_Exception $e) {
131:                         $text = $val[$key];
132:                     }
133:                 } elseif (in_array($header, $mime) && !empty($ob['p'])) {
134:                     /* MIME encoded headers (RFC 2231). */
135:                     $text = $val[$key];
136:                     foreach ($ob['p'] as $name => $param) {
137:                         foreach (Horde_Mime::encodeParam($name, Horde_String::convertCharset($param, 'UTF-8', $charset), $charset, array('escape' => true)) as $name2 => $param2) {
138:                             $text .= '; ' . $name2 . '=' . $param2;
139:                         }
140:                     }
141:                 } else {
142:                     $text = $charset
143:                         ? Horde_Mime::encode(Horde_String::convertCharset($val[$key], 'UTF-8', $charset), $charset)
144:                         : $val[$key];
145:                 }
146: 
147:                 if (empty($options['nowrap'])) {
148:                     /* Remove any existing linebreaks and wrap the line. */
149:                     $header_text = $ob['h'] . ': ';
150:                     $text = ltrim(substr(wordwrap($header_text . strtr(trim($text), array("\r" => '', "\n" => '')), 76, $eol . ' '), strlen($header_text)));
151:                 }
152: 
153:                 $val[$key] = $text;
154:             }
155: 
156:             $ret[$ob['h']] = (count($val) == 1) ? reset($val) : $val;
157:         }
158: 
159:         return $ret;
160:     }
161: 
162:     /**
163:      * Returns the internal header array in string format.
164:      *
165:      * @param array $options  Optional parameters:
166:      *   - canonical: (boolean) Use canonical (RFC 822/2045) line endings?
167:      *                DEFAULT: Uses $this->_eol
168:      *   - charset: (string) Encodes the headers using this charset.
169:      *              DEFAULT: No encoding.
170:      *   - defserver: (string) The default domain to append to mailboxes.
171:      *                DEFAULT: No default name.
172:      *   - nowrap: (integer) Don't wrap the headers.
173:      *             DEFAULT: Headers are wrapped.
174:      *
175:      * @return string  The headers in string format.
176:      */
177:     public function toString(array $options = array())
178:     {
179:         $eol = empty($options['canonical'])
180:             ? $this->_eol
181:             : "\r\n";
182:         $text = '';
183: 
184:         foreach ($this->toArray($options) as $key => $val) {
185:             if (!is_array($val)) {
186:                 $val = array($val);
187:             }
188:             foreach ($val as $entry) {
189:                 $text .= $key . ': ' . $entry . $eol;
190:             }
191:         }
192: 
193:         return $text . $eol;
194:     }
195: 
196:     /**
197:      * Generate the 'Received' header for the Web browser->Horde hop
198:      * (attempts to conform to guidelines in RFC 5321 [4.4]).
199:      *
200:      * @param array $options  Additional options:
201:      *   - dns: (Net_DNS2_Resolver) Use the DNS resolver object to lookup
202:      *          hostnames.
203:      *          DEFAULT: Use gethostbyaddr() function.
204:      *   - server: (string) Use this server name.
205:      *             DEFAULT: Auto-detect using current PHP values.
206:      */
207:     public function addReceivedHeader($options = array())
208:     {
209:         $old_error = error_reporting(0);
210:         if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
211:             /* This indicates the user is connecting through a proxy. */
212:             $remote_path = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
213:             $remote_addr = $remote_path[0];
214:             if (!empty($options['dns'])) {
215:                 $remote = $remote_addr;
216:                 try {
217:                     if ($response = $options['dns']->query($remote_addr, 'PTR')) {
218:                         foreach ($response->answer as $val) {
219:                             if (isset($val->ptrdname)) {
220:                                 $remote = $val->ptrdname;
221:                                 break;
222:                             }
223:                         }
224:                     }
225:                 } catch (Net_DNS2_Exception $e) {}
226:             } else {
227:                 $remote = gethostbyaddr($remote_addr);
228:             }
229:         } else {
230:             $remote_addr = $_SERVER['REMOTE_ADDR'];
231:             if (empty($_SERVER['REMOTE_HOST'])) {
232:                 if (!empty($options['dns'])) {
233:                     $remote = $remote_addr;
234:                     try {
235:                         if ($response = $options['dns']->query($remote_addr, 'PTR')) {
236:                             foreach ($response->answer as $val) {
237:                                 if (isset($val->ptrdname)) {
238:                                     $remote = $val->ptrdname;
239:                                     break;
240:                                 }
241:                             }
242:                         }
243:                     } catch (Net_DNS2_Exception $e) {}
244:                 } else {
245:                     $remote = gethostbyaddr($remote_addr);
246:                 }
247:             } else {
248:                 $remote = $_SERVER['REMOTE_HOST'];
249:             }
250:         }
251:         error_reporting($old_error);
252: 
253:         if (!empty($_SERVER['REMOTE_IDENT'])) {
254:             $remote_ident = $_SERVER['REMOTE_IDENT'] . '@' . $remote . ' ';
255:         } elseif ($remote != $_SERVER['REMOTE_ADDR']) {
256:             $remote_ident = $remote . ' ';
257:         } else {
258:             $remote_ident = '';
259:         }
260: 
261:         if (!empty($options['server'])) {
262:             $server_name = $options['server'];
263:         } elseif (!empty($_SERVER['SERVER_NAME'])) {
264:             $server_name = $_SERVER['SERVER_NAME'];
265:         } elseif (!empty($_SERVER['HTTP_HOST'])) {
266:             $server_name = $_SERVER['HTTP_HOST'];
267:         } else {
268:             $server_name = 'unknown';
269:         }
270: 
271:         $received = 'from ' . $remote . ' (' . $remote_ident .
272:             '[' . $remote_addr . ']) ' .
273:             'by ' . $server_name . ' (Horde Framework) with HTTP; ' .
274:             date('r');
275: 
276:         $this->addHeader('Received', $received);
277:     }
278: 
279:     /**
280:      * Generate the 'Message-ID' header.
281:      */
282:     public function addMessageIdHeader()
283:     {
284:         $this->addHeader('Message-ID', Horde_Mime::generateMessageId());
285:     }
286: 
287:     /**
288:      * Generate the user agent description header.
289:      */
290:     public function addUserAgentHeader()
291:     {
292:         $this->addHeader('User-Agent', $this->getUserAgent());
293:     }
294: 
295:     /**
296:      * Returns the user agent description header.
297:      *
298:      * @return string  The user agent header.
299:      */
300:     public function getUserAgent()
301:     {
302:         if (is_null($this->_agent)) {
303:             $this->_agent = 'Horde Application Framework 4';
304:         }
305:         return $this->_agent;
306:     }
307: 
308:     /**
309:      * Explicitly sets the User-Agent string.
310:      *
311:      * @param string $agent  The User-Agent string to use.
312:      */
313:     public function setUserAgent($agent)
314:     {
315:         $this->_agent = $agent;
316:     }
317: 
318:     /**
319:      * Add a header to the header array.
320:      *
321:      * @param string $header  The header name.
322:      * @param string $value   The header value.
323:      * @param array $options  Additional options:
324:      *   - charset: (string) Charset of the header value.
325:      *              DEFAULT: UTF-8
326:      *   - decode: (boolean) MIME decode the value?
327:      *             DEFAULT: false
328:      *   - params: (array) MIME parameters for Content-Type or
329:      *             Content-Disposition.
330:      *             DEFAULT: None
331:      */
332:     public function addHeader($header, $value, array $options = array())
333:     {
334:         $header = trim($header);
335:         $lcHeader = Horde_String::lower($header);
336: 
337:         if (!isset($this->_headers[$lcHeader])) {
338:             $this->_headers[$lcHeader] = array(
339:                 'h' => $header
340:             );
341:         }
342:         $ptr = &$this->_headers[$lcHeader];
343: 
344:         if (empty($options['decode'])) {
345:             if (!empty($options['charset'])) {
346:                 $value = Horde_String::convertCharset($value, $options['charset'], 'UTF-8');
347:             }
348:         } else {
349:             // Fields defined in RFC 2822 that contain address information
350:             if (in_array($lcHeader, $this->addressFields())) {
351:                 try {
352:                     $value = Horde_Mime::decodeAddrString($value, empty($options['charset']) ? 'UTF-8' : $options['charset']);
353:                 } catch (Horde_Mime_Exception $e) {
354:                     $value = '';
355:                 }
356:             } else {
357:                 $value = Horde_Mime::decode($value, empty($options['charset']) ? 'UTF-8' : $options['charset']);
358:             }
359:         }
360: 
361:         if (isset($ptr['v'])) {
362:             if (!is_array($ptr['v'])) {
363:                 $ptr['v'] = array($ptr['v']);
364:             }
365:             $ptr['v'][] = $value;
366:         } else {
367:             $ptr['v'] = $value;
368:         }
369: 
370:         if (!empty($options['params'])) {
371:             $ptr['p'] = $options['params'];
372:         }
373:     }
374: 
375:     /**
376:      * Remove a header from the header array.
377:      *
378:      * @param string $header  The header name.
379:      */
380:     public function removeHeader($header)
381:     {
382:         unset($this->_headers[Horde_String::lower(trim($header))]);
383:     }
384: 
385:     /**
386:      * Replace a value of a header.
387:      *
388:      * @param string $header  The header name.
389:      * @param string $value   The header value.
390:      * @param array $options  Additional options:
391:      *   - charset: (string) Charset of the header value.
392:      *              DEFAULT: UTF-8
393:      *   - decode: (boolean) MIME decode the value?
394:      *             DEFAULT: false
395:      *   - params: (array) MIME parameters for Content-Type or
396:      *             Content-Disposition.
397:      *             DEFAULT: None
398:      */
399:     public function replaceHeader($header, $value, $options = array())
400:     {
401:         $this->removeHeader($header);
402:         $this->addHeader($header, $value, $options);
403:     }
404: 
405:     /**
406:      * Set a value for a particular header ONLY if that header is set.
407:      *
408:      * @param string $header  The header name.
409:      * @param string $value   The header value.
410:      * @param array $options  Additional options:
411:      *   - charset: (string) Charset of the header value.
412:      *              DEFAULT: UTF-8
413:      *   - decode: (boolean) MIME decode the value?
414:      *   - params: (array) MIME parameters for Content-Type or
415:      *             Content-Disposition.
416:      *
417:      * @return boolean  True if value was set.
418:      */
419:     public function setValue($header, $value, $options = array())
420:     {
421:         if (isset($this->_headers[Horde_String::lower($header)])) {
422:             $this->addHeader($header, $value, $options);
423:             return true;
424:         }
425: 
426:         return false;
427:     }
428: 
429:     /**
430:      * Attempts to return the header in the correct case.
431:      *
432:      * @param string $header  The header to search for.
433:      *
434:      * @return string  The value for the given header.
435:      *                 If the header is not found, returns null.
436:      */
437:     public function getString($header)
438:     {
439:         $lcHeader = Horde_String::lower($header);
440:         return (isset($this->_headers[$lcHeader]))
441:             ? $this->_headers[$lcHeader]['h']
442:             : null;
443:     }
444: 
445:     /**
446:      * Attempt to return the value for a given header.
447:      * The following header fields can only have 1 entry, so if duplicate
448:      * entries exist, the first value will be used:
449:      *   * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, In-Reply-To,
450:      *     References, Subject (RFC 2822 [3.6])
451:      *   * All List Headers (RFC 2369 [3])
452:      * The values are not MIME encoded.
453:      *
454:      * @param string $header  The header to search for.
455:      * @param integer $type   The type of return:
456:      *   - VALUE_STRING: Returns a string representation of the entire header.
457:      *   - VALUE_BASE: Returns a string representation of the base value of
458:      *                 the header. If this is not a header that allows
459:      *                 parameters, this will be equivalent to VALUE_STRING.
460:      *   - VALUE_PARAMS: Returns the list of parameters for this header. If
461:      *                   this is not a header that allows parameters, this
462:      *                   will be an empty array.
463:      *
464:      * @return mixed  The value for the given header.
465:      *                If the header is not found, returns null.
466:      */
467:     public function getValue($header, $type = self::VALUE_STRING)
468:     {
469:         $header = Horde_String::lower($header);
470: 
471:         if (!isset($this->_headers[$header])) {
472:             return null;
473:         }
474: 
475:         $ptr = &$this->_headers[$header];
476:         if (is_array($ptr['v']) &&
477:             in_array($header, $this->singleFields(true))) {
478:             if (in_array($header, $this->addressFields())) {
479:                 $base = str_replace(';,', ';', implode(', ', $ptr['v']));
480:             } else {
481:                 $base = $ptr['v'][0];
482:             }
483:         } else {
484:             $base = $ptr['v'];
485:         }
486:         $params = isset($ptr['p']) ? $ptr['p'] : array();
487: 
488:         switch ($type) {
489:         case self::VALUE_BASE:
490:             return $base;
491: 
492:         case self::VALUE_PARAMS:
493:             return $params;
494: 
495:         case self::VALUE_STRING:
496:             foreach ($params as $key => $val) {
497:                 $base .= '; ' . $key . '=' . $val;
498:             }
499:             return $base;
500:         }
501:     }
502: 
503:     /**
504:      * Returns the list of RFC defined header fields that contain address
505:      * info.
506:      *
507:      * @return array  The list of headers, in lowercase.
508:      */
509:     static public function addressFields()
510:     {
511:         return array(
512:             'from', 'to', 'cc', 'bcc', 'reply-to', 'resent-to', 'resent-cc',
513:             'resent-bcc', 'resent-from', 'sender'
514:         );
515:     }
516: 
517:     /**
518:      * Returns the list of RFC defined header fields that can only contain
519:      * a single value.
520:      *
521:      * @param boolean $list  Return list-related headers also?
522:      *
523:      * @return array  The list of headers, in lowercase.
524:      */
525:     public function singleFields($list = true)
526:     {
527:         return $list
528:             ? array_merge($this->_singleFields, array_keys($this->listHeaders()))
529:             : $this->_singleFields;
530:     }
531: 
532:     /**
533:      * Returns the list of RFC defined MIME header fields that may contain
534:      * parameter info.
535:      *
536:      * @return array  The list of headers, in lowercase.
537:      */
538:     static public function mimeParamFields()
539:     {
540:         return array('content-type', 'content-disposition');
541:     }
542: 
543:     /**
544:      * Returns the list of valid mailing list headers.
545:      *
546:      * @return array  The list of valid mailing list headers.
547:      */
548:     static public function listHeaders()
549:     {
550:         return array(
551:             /* RFC 2369 */
552:             'list-help'         =>  Horde_Mime_Translation::t("List-Help"),
553:             'list-unsubscribe'  =>  Horde_Mime_Translation::t("List-Unsubscribe"),
554:             'list-subscribe'    =>  Horde_Mime_Translation::t("List-Subscribe"),
555:             'list-owner'        =>  Horde_Mime_Translation::t("List-Owner"),
556:             'list-post'         =>  Horde_Mime_Translation::t("List-Post"),
557:             'list-archive'      =>  Horde_Mime_Translation::t("List-Archive"),
558:             /* RFC 2919 */
559:             'list-id'           =>  Horde_Mime_Translation::t("List-Id")
560:         );
561:     }
562: 
563:     /**
564:      * Do any mailing list headers exist?
565:      *
566:      * @return boolean  True if any mailing list headers exist.
567:      */
568:     public function listHeadersExist()
569:     {
570:         return (bool)count(array_intersect(array_keys($this->listHeaders()), array_keys($this->_headers)));
571:     }
572: 
573:     /**
574:      * Sets a new string to use for EOLs.
575:      *
576:      * @param string $eol  The string to use for EOLs.
577:      */
578:     public function setEOL($eol)
579:     {
580:         $this->_eol = $eol;
581:     }
582: 
583:     /**
584:      * Get the string to use for EOLs.
585:      *
586:      * @return string  The string to use for EOLs.
587:      */
588:     public function getEOL()
589:     {
590:         return $this->_eol;
591:     }
592: 
593:     /**
594:      * Returns a header from the header object.
595:      *
596:      * @param string $field  The header to return as an object.
597:      *
598:      * @return array  The object for the field requested.
599:      * @see Horde_Mime_Address::parseAddressList()
600:      */
601:     public function getOb($field)
602:     {
603:         $val = $this->getValue($field);
604:         if (!is_null($val)) {
605:             try {
606:                 return Horde_Mime_Address::parseAddressList($val);
607:             } catch (Horde_Mime_Exception $e) {}
608:         }
609:         return array();
610:     }
611: 
612:     /**
613:      * Builds a Horde_Mime_Headers object from header text.
614:      * This function can be called statically:
615:      *   $headers = Horde_Mime_Headers::parseHeaders().
616:      *
617:      * @param string $text  A text string containing the headers.
618:      *
619:      * @return Horde_Mime_Headers  A new Horde_Mime_Headers object.
620:      */
621:     static public function parseHeaders($text)
622:     {
623:         $currheader = $currtext = null;
624:         $mime = self::mimeParamFields();
625:         $to_process = array();
626: 
627:         foreach (explode("\n", $text) as $val) {
628:             $val = rtrim($val);
629:             if (empty($val)) {
630:                 break;
631:             }
632: 
633:             if (($val[0] == ' ') || ($val[0] == "\t")) {
634:                 $currtext .= ' ' . ltrim($val);
635:             } else {
636:                 if (!is_null($currheader)) {
637:                     $to_process[] = array($currheader, $currtext);
638:                 }
639: 
640:                 $pos = strpos($val, ':');
641:                 $currheader = substr($val, 0, $pos);
642:                 $currtext = ltrim(substr($val, $pos + 1));
643:             }
644:         }
645: 
646:         if (!is_null($currheader)) {
647:             $to_process[] = array($currheader, $currtext);
648:         }
649: 
650:         $headers = new Horde_Mime_Headers();
651: 
652:         reset($to_process);
653:         while (list(,$val) = each($to_process)) {
654:             /* Ignore empty headers. */
655:             if (!strlen($val[1])) {
656:                 continue;
657:             }
658: 
659:             $val[1] = self::sanityCheck($val[0], $val[1]);
660: 
661:             if (in_array(Horde_String::lower($val[0]), $mime)) {
662:                 $res = Horde_Mime::decodeParam($val[0], $val[1], 'UTF-8');
663:                 $headers->addHeader($val[0], $res['val'], array(
664:                     'decode' => true,
665:                     'params' => $res['params']
666:                 ));
667:             } else {
668:                 $headers->addHeader($val[0], $val[1], array(
669:                     'decode' => true
670:                 ));
671:             }
672:         }
673: 
674:         return $headers;
675:     }
676: 
677:     /**
678:      * Perform sanity checking on a raw header (e.g. handle 8-bit characters).
679:      * This function can be called statically:
680:      *   $headers = Horde_Mime_Headers::sanityCheck().
681:      *
682:      * @since Horde_Mime 1.4.0
683:      *
684:      * @param string $header  The header.
685:      * @param string $data    The header data.
686:      * @param array $opts     Optional parameters:
687:      *   - encode: (boolean) If true, will MIME encode any 8-bit characters.
688:      *             If false (default), converts the text to UTF-8.
689:      *
690:      * @return string  The cleaned header data.
691:      */
692:     static public function sanityCheck($header, $data, array $opts = array())
693:     {
694:         $charset_test = array(
695:             'UTF-8',
696:             'windows-1252',
697:             self::$defaultCharset
698:         );
699: 
700:         if (Horde_Mime::is8bit($data)) {
701:             /* Assumption: broken charset in headers is generally either
702:              * UTF-8 or ISO-8859-1/Windows-1252. Test these charsets
703:              * first before using default charset. This may be a
704:              * Western-centric approach, but it's better than nothing. */
705:             foreach ($charset_test as $charset) {
706:                 $tmp = Horde_String::convertCharset($data, $charset, 'UTF-8');
707:                 if (Horde_String::validUtf8($tmp)) {
708:                     break;
709:                 }
710:             }
711: 
712:             if (empty($opts['encode'])) {
713:                 $data = $tmp;
714:             } else {
715:                 $header = Horde_String::lower($header);
716:                 if (in_array($header, self::addressFields())) {
717:                     $data = Horde_Mime::encodeAddress($tmp, $charset);
718:                 } elseif (in_array($header, self::mimeParamFields())) {
719:                     $res = Horde_Mime::decodeParam($header, $tmp, 'UTF-8');
720:                     $data = $res['val'];
721:                     foreach ($res['params'] as $name => $param) {
722:                         foreach (Horde_Mime::encodeParam($name, $param, 'UTF-8', array('escape' => true)) as $name2 => $param2) {
723:                             $data .= '; ' . $name2 . '=' . $param2;
724:                         }
725:                     }
726:                 } else {
727:                     $data = Horde_Mime::encode($tmp, 'UTF-8');
728:                 }
729:             }
730:         }
731: 
732:         return $data;
733:     }
734: 
735:     /* Serializable methods. */
736: 
737:     /**
738:      * Serialization.
739:      *
740:      * @return string  Serialized data.
741:      */
742:     public function serialize()
743:     {
744:         $data = array(
745:             // Serialized data ID.
746:             self::VERSION,
747:             $this->_headers,
748:             $this->_eol
749:         );
750: 
751:         if (!is_null($this->_agent)) {
752:             $data[] = $this->_agent;
753:         }
754: 
755:         return serialize($data);
756:     }
757: 
758:     /**
759:      * Unserialization.
760:      *
761:      * @param string $data  Serialized data.
762:      *
763:      * @throws Exception
764:      */
765:     public function unserialize($data)
766:     {
767:         $data = @unserialize($data);
768:         if (!is_array($data) ||
769:             !isset($data[0]) ||
770:             ($data[0] != self::VERSION)) {
771:             throw new Horde_Mime_Exception('Cache version change');
772:         }
773: 
774:         $this->_headers = $data[1];
775:         $this->_eol = $data[2];
776:         if (isset($data[3])) {
777:             $this->_agent = $data[3];
778:         }
779:     }
780: 
781: }
782: 
API documentation generated by ApiGen