- * function foldLine - for cleanly folding of headerlines
- *
- * @param string $line
- * @param integer $length length to fold the line at
- * @param string $pre prefix the line with...
- *
- * @return string $line folded line with trailing CRLF
- */
- function foldLine($line, $length, $pre='') {
- $line = substr($line,0, -2);
- $length -= 2; /* do not fold between \r and \n */
- $cnt = strlen($line);
- if ($cnt > $length) { /* try folding */
- $fold_string = "\r\n " . $pre;
- $bFirstFold = false;
- $aFoldLine = array();
- while (strlen($line) > $length) {
- $fold = false;
- /* handle encoded parts */
- if (preg_match('/(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(\s+|.*)/Ui',$line,$regs)) {
- $fold_tmp = $regs[1];
- if (!trim($regs[5])) {
- $fold_tmp .= $regs[5];
- }
- $iPosEnc = strpos($line,$fold_tmp);
- $iLengthEnc = strlen($fold_tmp);
- $iPosEncEnd = $iPosEnc+$iLengthEnc;
- if ($iPosEnc < $length && (($iPosEncEnd) > $length)) {
- $fold = true;
- /* fold just before the start of the encoded string */
- if ($iPosEnc) {
- $aFoldLine[] = substr($line,0,$iPosEnc);
- }
- $line = substr($line,$iPosEnc);
- if (!$bFirstFold) {
- $bFirstFold = true;
- $length -= strlen($fold_string);
- }
- if ($iLengthEnc > $length) { /* place the encoded
- string on a separate line and do not fold inside it*/
- /* minimize foldstring */
- $fold_string = "\r\n ";
- $aFoldLine[] = substr($line,0,$iLengthEnc);
- $line = substr($line,$iLengthEnc);
+ * Fold header lines per RFC 2822/2.2.3 and RFC 822/3.1.1
+ *
+ * Herein "soft" folding/wrapping (with whitespace tokens) is
+ * what we refer to as the preferred method of wrapping - that
+ * which we'd like to do within the $soft_wrap limit, but if
+ * not possible, we will try to do as soon as possible after
+ * $soft_wrap up to the $hard_wrap limit. Encoded words don't
+ * need to be detected in this phase, since they cannot contain
+ * spaces.
+ *
+ * "Hard" folding/wrapping (with "hard" tokens) is what we refer
+ * to as less ideal wrapping that will be done to keep within
+ * the $hard_wrap limit. This adds other syntactical breaking
+ * elements such as commas and encoded words.
+ *
+ * @param string $header The header content being folded
+ * @param integer $soft_wrap The desirable maximum line length
+ * (OPTIONAL; default is 78, per RFC)
+ * @param string $indent Wrapped lines will already have
+ * whitespace following the CRLF wrap,
+ * but you can add more indentation (or
+ * whatever) with this. The use of this
+ * parameter is DISCOURAGED, since it
+ * can corrupt the redisplay (unfolding)
+ * of headers whose content is space-
+ * sensitive, like subjects, etc.
+ * (OPTIONAL; default is an empty string)
+ * @param string $hard_wrap The absolute maximum line length
+ * (OPTIONAL; default is 998, per RFC)
+ *
+ * @return string The folded header content, with a trailing CRLF.
+ *
+ */
+ function foldLine($header, $soft_wrap=78, $indent='', $hard_wrap=998) {
+
+ // the "hard" token list can be altered if desired,
+ // for example, by adding ":"
+ // (in the future, we can take optional arguments
+ // for overriding or adding elements to the "hard"
+ // token list if we want to get fancy)
+ //
+ // the order of these is significant - preferred
+ // fold points should be listed first
+ //
+ // it is advised that the "=" always come first
+ // since it also finds encoded words, thus if it
+ // comes after some other token that happens to
+ // fall within the encoded word, the encoded word
+ // could be inadvertently broken in half, which
+ // is not allowable per RFC
+ //
+ $hard_break_tokens = array(
+ '=', // includes encoded word detection
+ ',',
+ ';',
+ );
+
+ // the order of these is significant too
+ //
+ $whitespace = array(
+ ' ',
+ "\t",
+ );
+
+ $CRLF = "\r\n";
+
+ $folded_header = '';
+
+ // if using an indent string, reduce wrap limits by its size
+ //
+ if (!empty($indent)) {
+ $soft_wrap -= strlen($indent);
+ $hard_wrap -= strlen($indent);
+ }
+
+ while (strlen($header) > $soft_wrap) {
+
+ $soft_wrapped_line = substr($header, 0, $soft_wrap);
+
+ // look for a token as close to the end of the soft wrap limit as possible
+ //
+ foreach ($whitespace as $token) {
+
+ // note that this if statement also fails when $pos === 0,
+ // which is intended, since blank lines are not allowed
+ //
+ if ($pos = strrpos($soft_wrapped_line, $token))
+ {
+ $new_fold = substr($header, 0, $pos);
+
+ // make sure proposed fold doesn't create a blank line
+ //
+ if (!trim($new_fold)) continue;
+
+ // with whitespace breaks, we fold BEFORE the token
+ //
+ $folded_header .= $new_fold . $CRLF . $indent;
+ $header = substr($header, $pos);
+
+ // ready for next while() iteration
+ //
+ continue 2;
+
+ }
+
+ }
+
+ // we were unable to find a wrapping point within the soft
+ // wrap limit, so now we'll try to find the first possible
+ // soft wrap point within the hard wrap limit
+ //
+ $hard_wrapped_line = substr($header, 0, $hard_wrap);
+
+ // look for a *SOFT* token as close to the
+ // beginning of the hard wrap limit as possible
+ //
+ foreach ($whitespace as $token) {
+
+ // use while loop instead of if block because it
+ // is possible we don't want the first one we find
+ //
+ $pos = $soft_wrap - 1; // -1 is corrected by +1 on next line
+ while ($pos = strpos($hard_wrapped_line, $token, $pos + 1))
+ {
+
+ $new_fold = substr($header, 0, $pos);
+
+ // make sure proposed fold doesn't create a blank line
+ //
+ if (!trim($new_fold)) continue;
+
+ // with whitespace breaks, we fold BEFORE the token
+ //
+ $folded_header .= $new_fold . $CRLF . $indent;
+ $header = substr($header, $pos);
+
+ // ready for next outter while() iteration
+ //
+ continue 3;
+
+ }
+
+ }
+
+ // we were still unable to find a soft wrapping point within
+ // both the soft and hard wrap limits, so if the length of
+ // what is left is no more than the hard wrap limit, we'll
+ // simply take the whole thing
+ //
+ if (strlen($header) <= strlen($hard_wrapped_line))
+ break;
+
+ // otherwise, we can't quit yet - look for a "hard" token
+ // as close to the end of the hard wrap limit as possible
+ //
+ foreach ($hard_break_tokens as $token) {
+
+ // note that this if statement also fails when $pos === 0,
+ // which is intended, since blank lines are not allowed
+ //
+ if ($pos = strrpos($hard_wrapped_line, $token))
+ {
+
+ // if we found a "=" token, we must determine whether,
+ // if it is part of an encoded word, it is the beginning
+ // or middle of one, where we need to readjust $pos a bit
+ //
+ if ($token == '=') {
+
+ // if we found the beginning of an encoded word,
+ // we want to break BEFORE the token
+ //
+ if (preg_match('/^(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)/Ui',
+ substr($header, $pos))) {
+ $pos--;