public function testConstruct() { $err = new \r8\Exception\Data('lorm ipsum', 'Nonsense', 'An error occured with this data', 404); $this->assertEquals("An error occured with this data", $err->getMessage()); $this->assertEquals(404, $err->getCode()); $this->assertEquals(array("Nonsense" => "string('lorm ipsum')"), $err->getData()); }
/** * Encodes a string * * @param mixed $value The value to encode * @return mixed The result of the encoding process */ public function to($string) { // Yes, I realize that the iconv methods can handle this, but this method // can do one thing the iconv encode method cant: Handle encoding without // a header defined $string = (string) $string; // React to the input encoding $string = iconv($this->getInputEncoding(), $this->getOutputEncoding(), $string); // Generate the part that describes the encoding $encodingPart = "=?" . $this->getOutputEncoding() . "?B?"; // Do the actual encoding $string = base64_encode($string); // If we aren't doing any wrapping, take the easy out if ($this->getLineLength() == FALSE) { return ($this->headerExists() ? $this->getHeader() . ": " : "") . $encodingPart . $string . "?="; } // If there is a header to attach, then we need to figure out how many // characters will fit on the first line with it if ($this->headerExists()) { // If the header is so long it won't fit on a line (plus one for the colon) if ($this->getLineLength() < strlen($this->getHeader()) + 1) { $err = new \r8\Exception\Data($this->getHeader(), "MIME Header", "Header length exceeds the maximum line length"); $err->addData("Max Line Length", $this->getLineLength()); throw $err; } // Line length, minus the Header length, minus two for the colon and // space, minus the length of the encoding definition, minus two for // the trailing ?= $firstLineLength = $this->getLineLength() - strlen($this->getHeader()) - 2 - strlen($encodingPart) - 2; // Force it to a multiple of 4 $firstLineLength = floor($firstLineLength / 4) * 4; $prepend = $this->getHeader() . ":"; // If there is room on the first line for at least four characters, // then add them on if ($firstLineLength > 0) { $prepend .= " " . $encodingPart . substr($string, 0, $firstLineLength) . "?="; $string = substr($string, $firstLineLength); // If it all fits on the first line, lets get out of here if ($string == "") { return $prepend; } } $prepend .= $this->getEOL() . "\t" . $encodingPart; } else { $prepend = $encodingPart; } // The line length, minus one to compensate for the leading fold, minus // the length of the encoding definition, minus two for the trailing ?= $lineLength = $this->getLineLength() - 1 - strlen($encodingPart) - 2; // Force it to a multiple of four $lineLength = floor($lineLength / 4) * 4; // If the required data won't fit on a line, throw an error if ($lineLength <= 0) { throw new \r8\Exception\Data($this->getLineLength(), "Max Line Length", "Required content length exceeds the maximum line length"); } return $prepend . implode("?=" . $this->getEOL() . "\t" . $encodingPart, str_split($string, $lineLength)) . "?="; }
/** * Renders the template and returns it as a string * * @return String Returns the rendered template as a string */ public function render() { $self = $this; return preg_replace_callback($this->search, function ($matches) use($self) { // Ensure that the search string returns the proper number of matches if (count($matches) < 4) { $err = new \r8\Exception\Data($self->search, "Search Regular Expression", "Must return at least 3 groupings"); $err->addData("Groupings", count($matches) - 1); throw $err; } // Handle escaped replace strings if (strlen($matches[1]) % 2 != 0) { return substr($matches[1], 0, -1) . $matches[2]; } else { if (!$self->exists($matches[3]) || $self->get($matches[3]) === $self) { return $matches[1]; } else { return $matches[1] . (string) $self->get($matches[3]); } } }, $this->template); }
/** * Decodes an encoded string * * @param mixed $value The value to decode * @return mixed The original, unencoded value */ public function from($string) { $string = trim((string) $string); $string = \r8\str\stripHead($string, "<~"); $string = \r8\str\stripTail($string, "~>"); $length = strlen($string); $result = ""; $tuple = 0; $j = 0; // Loop over each character in the input for ($i = 0; $i < $length; $i++) { $chr = $string[$i]; if ($j != 0 && ($chr == "z" || $chr == "y")) { $err = new \r8\Exception\Data($chr, "Unexpected Character", "Misplaced compression character"); $err->addData("Encoded String", $string); throw $err; } switch ($chr) { // Add this character to the tuple default: // Grab the character code and ensure it is a valid character $ord = ord($chr) - 33; if ($ord < 0 || $ord > 84) { $err = new \r8\Exception\Data($chr, "Invalid Character", "Invalid encoding character"); $err->addData("Encoded String", $string); throw $err; } // Integrate this character into the tuple $tuple += $ord * pow(85, 4 - $j); // If this isn't the last character in the tuple, move on if ($j < 4 && $i != $length - 1) { $j++; } else { // Compensate for an incomplete trailing tuple if ($j < 4) { for ($k = $j; $k <= 3; $k++) { $tuple += 85 * pow(85, 3 - $k); } } // Convert the 32bit integer to binary form $tuple = str_pad(decbin($tuple), 32, "0", STR_PAD_LEFT); // Split the binary into 8 bit segments and convert each back to an integer $tuple = array_map("bindec", str_split($tuple, 8)); if ($j < 4) { $tuple = array_slice($tuple, 0, $j); } // Convert each int into a character, then combine them $result .= implode("", array_map("chr", $tuple)); // Reset the tuple to prepare for the next substring $tuple = 0; $j = 0; } break; // Handle z compression // Handle z compression case "z": $result .= chr(0) . chr(0) . chr(0) . chr(0); break; // Handle y compression // Handle y compression case "y": $result .= " "; break; // Skip over white space // Skip over white space case "\n": case "\r": case "\t": case " ": case "": case "\f": case "": break; } } return $result; }
/** * Encodes a string * * @param mixed $value The value to encode * @return mixed The result of the encoding process */ public function to($string) { // Yes, I realize that the iconv methods can handle this, but this method // can do a few things the iconv encode method cant: Handle encoding without // a header defined, use the underscore as spaces to save on space. $string = (string) $string; // React to the input encoding $string = iconv($this->getInputEncoding(), $this->getOutputEncoding(), $string); // This will hold the resulting string $result = ""; // Generate the part that describes the encoding $encodingPart = "=?" . $this->getOutputEncoding() . "?Q?"; // This is a running total of the length of the current line // Once this reaches the max length, the line will be wrapped $currentLine = 0; if ($this->headerExists()) { $result = $this->headerExists() ? $this->getHeader() . ":" : ""; // If the header is so long it won't fit on a line (plus one for the colon) if ($this->getLineLength() !== FALSE && $this->getLineLength() < strlen($result) + 1) { $err = new \r8\Exception\Data($this->getHeader(), "MIME Header", "Header length exceeds the maximum line length"); $err->addData("Max Line Length", $this->getLineLength()); throw $err; } // If the encoding part and the header can't fit on the same line, // plus one for the space that hasn't been added yet, plus two for the trailing '?=', // plus 3 for the length of a single encoded character if ($this->getLineLength() !== FALSE && strlen($result) + strlen($encodingPart) + 1 + 2 + 3 > $this->getLineLength()) { $result .= $this->getEOL() . "\t"; } else { $result .= " "; // Adjust the offset of the current line to compensate for the length of the header $currentLine += strlen($result) - 1; } } if ($this->getLineLength() === FALSE) { $maxLineLength = FALSE; } else { // The max line length is the line length, minus one for the fold, // minus the length of the encoding definition, minus two for the closing ?= $maxLineLength = $this->getLineLength() - 1 - strlen($encodingPart) - 2; } if ($maxLineLength !== FALSE && $maxLineLength <= 0) { throw new \r8\Exception\Data($this->getLineLength(), "Max Line Length", "Required content length exceeds the maximum line length"); } // Attach the leading encoding info $result .= $encodingPart; // Grab the string length only once $stringLength = strlen($string); // Iterate over each character of the string we're encoding for ($i = 0; $i < $stringLength; $i++) { // Replace spaces with underscores if ($string[$i] == " ") { $result .= "_"; $currentLine++; } else { if (ord($string[$i]) <= 32 || ord($string[$i]) >= 127 || $string[$i] == "=" || $string[$i] == "?" || $string[$i] == "_" || $string[$i] == ":") { $result .= "=" . strtoupper(str_pad(dechex(ord($string[$i])), 2, "0", STR_PAD_LEFT)); $currentLine += 3; } else { $result .= $string[$i]; $currentLine++; } } if ($maxLineLength !== FALSE) { // If we have reached the max characters in a line, and this isn't // the final character in the string, then wrap if ($currentLine >= $maxLineLength && $i != $stringLength - 1) { $result .= "?=" . $this->getEOL() . "\t" . $encodingPart; $currentLine = 0; } } } return $result . "?="; }