1: <?php
2:
3: /**
4: * Jyxo PHP Library
5: *
6: * LICENSE
7: *
8: * This source file is subject to the new BSD license that is bundled
9: * with this package in the file license.txt.
10: * It is also available through the world-wide-web at this URL:
11: * https://github.com/jyxo/php/blob/master/license.txt
12: */
13:
14: namespace Jyxo\Mail;
15:
16: /**
17: * List of possible content encodings.
18: *
19: * @category Jyxo
20: * @package Jyxo\Mail
21: * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
22: * @license https://github.com/jyxo/php/blob/master/license.txt
23: * @author Jaroslav HanslĂk
24: */
25: class Encoding
26: {
27: /**
28: * 8-bit encoding.
29: *
30: * @var string
31: */
32: const BIT8 = '8bit';
33:
34: /**
35: * 7-bit encoding.
36: *
37: * @var string
38: */
39: const BIT7 = '7bit';
40:
41: /**
42: * Binary encoding.
43: *
44: * @var string
45: */
46: const BINARY = 'binary';
47:
48: /**
49: * Base64 encoding.
50: *
51: * @var string
52: */
53: const BASE64 = 'base64';
54:
55: /**
56: * Quoted printable encoding.
57: *
58: * @var string
59: */
60: const QUOTED_PRINTABLE = 'quoted-printable';
61:
62: /**
63: * Constructor preventing from creating instances.
64: *
65: * @throws \LogicException When trying to create an instance
66: */
67: public final function __construct()
68: {
69: throw new \LogicException(sprintf('Cannot create an instance of a static class %s.', get_class($this)));
70: }
71:
72: /**
73: * Checks if the given encoding is compatible.
74: *
75: * @param string $encoding Encoding name
76: * @return boolean
77: */
78: public static function isCompatible($encoding)
79: {
80: static $encodings = array(
81: self::BIT7 => true,
82: self::BIT8 => true,
83: self::BINARY => true,
84: self::BASE64 => true,
85: self::QUOTED_PRINTABLE => true
86: );
87:
88: return isset($encodings[$encoding]);
89: }
90:
91: /**
92: * Encodes a string using the given encoding.
93: *
94: * @param string $string Input string
95: * @param string $encoding Encoding name
96: * @param integer $lineLength Line length
97: * @param string $lineEnd Line ending
98: * @return string
99: * @throws \InvalidArgumentException If an incompatible encoding was provided
100: */
101: public static function encode($string, $encoding, $lineLength, $lineEnd)
102: {
103: switch ($encoding) {
104: case self::BASE64:
105: return self::encodeBase64($string, $lineLength, $lineEnd);
106: case self::BIT7:
107: // Break missing intentionally
108: case self::BIT8:
109: return \Jyxo\String::fixLineEnding(trim($string), $lineEnd) . $lineEnd;
110: case self::QUOTED_PRINTABLE:
111: return self::encodeQuotedPrintable($string, $lineLength, $lineEnd);
112: case self::BINARY:
113: return $string;
114: default:
115: throw new \InvalidArgumentException(sprintf('Incompatible encoding %s.', $encoding));
116: }
117: }
118:
119: /**
120: * Encodes a string using the quoted-printable encoding.
121: *
122: * @param string $string Input string
123: * @param integer $lineLength Line length
124: * @param string $lineEnd Line ending
125: * @return string
126: */
127: private static function encodeQuotedPrintable($string, $lineLength, $lineEnd)
128: {
129: $encoded = \Jyxo\String::fixLineEnding(trim($string), $lineEnd);
130:
131: // Replaces all high ASCII characters, control codes and '='
132: $encoded = preg_replace_callback('~([\000-\010\013\014\016-\037\075\177-\377])~', function($matches) {
133: return '=' . sprintf('%02X', ord($matches[1]));
134: }, $encoded);
135:
136: // Replaces tabs and spaces if on line ends
137: $encoded = preg_replace_callback('~([\011\040])' . $lineEnd . '~', function($matches) use ($lineEnd) {
138: return '=' . sprintf('%02X', ord($matches[1])) . $lineEnd;
139: }, $encoded);
140:
141: $output = '';
142: $lines = explode($lineEnd, $encoded);
143: // Release from memory
144: unset($encoded);
145: foreach ($lines as $line) {
146: // Line length is less than maximum
147: if (strlen($line) <= $lineLength) {
148: $output .= $line . $lineEnd;
149: continue;
150: }
151:
152: do {
153: $partLength = strlen($line);
154: if ($partLength > $lineLength) {
155: $partLength = $lineLength;
156: }
157:
158: // Cannot break a line in the middle of a character
159: $pos = strrpos(substr($line, 0, $partLength), '=');
160: if ((false !== $pos) && ($pos >= $partLength - 2)) {
161: $partLength = $pos;
162: }
163:
164: // If the last char is a break, move one character backwards
165: if (($partLength > 0) && (' ' == $line[$partLength - 1])) {
166: $partLength--;
167: }
168:
169: // Saves string parts, trims the string and continues
170: $output .= substr($line, 0, $partLength);
171: $line = substr($line, $partLength);
172:
173: // We are in the middle of a line
174: if (!empty($line)) {
175: $output .= '=';
176: }
177: $output .= $lineEnd;
178: } while (!empty($line));
179: }
180:
181: return $output;
182: }
183:
184: /**
185: * Encodes a string using the base64 encoding.
186: *
187: * @param string $string Input string
188: * @param integer $lineLength Line length
189: * @param string $lineEnd Line ending
190: * @return string
191: */
192: private static function encodeBase64($string, $lineLength, $lineEnd)
193: {
194: return trim(chunk_split(base64_encode($string), $lineLength, $lineEnd));
195: }
196: }
197: