. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * yEnc PHP Class. * * This class provides functions to encode and decode yEnc files * and strings. It meets the specifications of version 1.3 of the * yEnc working draft (http://www.yenc.org/yenc-draft.1.3.txt) * and also incorporates several unofficial (but recommended) features such * as escaping of tab, space and period (.) characters. * * @author Ryan Grove (ryan\@wonko.com) * @date November 26, 2002 * @version 1.0.0 */ class Yenc { /** Text of the most recent error message (if any). */ var $error; /** * yEncodes a string and returns it. * * @param string $string String to encode. * @param string $filename Name to use as the filename in the yEnc header (this does not have to be an actual file). * @param int $linelen Line length to use (can be up to 254 characters). * @param bool $crc32 Set to true to include a CRC checksum in the trailer to allow decoders to verify data integrity. * * @return bool|string false on error or string (yEncoded). * * @see decode() */ function encode($string, $filename, $linelen = 128, $crc32 = true) { // yEnc 1.3 draft doesn't allow line lengths of more than 254 bytes. if ($linelen > 254) $linelen = 254; if ($linelen < 1) { $this->error = "$linelen is not a valid line length."; return false; } $encoded = ''; // Encode each character of the string one at a time. for( $i = 0; $i < strlen($string); $i++) { $value = (ord($string{$i}) + 42) % 256; // Escape NULL, TAB, LF, CR, space, . and = characters. if ($value == 0 || $value == 9 || $value == 10 || $value == 13 || $value == 32 || $value == 46 || $value == 61) $encoded .= "=".chr(($value + 64) % 256); else $encoded .= chr($value); } // Wrap the lines to $linelen characters // TODO: Make sure we don't split escaped characters in half, as per the yEnc spec. $encoded = trim(chunk_split($encoded, $linelen)); // Tack a yEnc header onto the encoded string. $encoded = "=ybegin line=$linelen size=".strlen($string)." name=".trim($filename)."\r\n".$encoded; $encoded .= "\r\n=yend size=".strlen($string); // Add a CRC32 checksum if desired. if ($crc32 === true) $encoded .= " crc32=".strtolower(sprintf("%04X", crc32($string))); return $encoded."\r\n"; } /** * yDecodes an encoded string and either writes the result to a file * or returns it as a string. * * @param string $string yEncoded string to decode. * @param string $destination Destination directory where the decoded file will * be written. This must be a valid directory with no trailing * slash to which PHP has write access. If destination is * not specified, the decoded file will be returned rather than * written to the disk. * * @return bool If destination is not set, the decoded file will be * returned as a string. Otherwise, true will be returned on * success. In either case, false will be returned on error. * * @see encode() */ function decode($string, $destination = "") { $encoded = $header = $trailer = array(); // Extract the yEnc string itself. preg_match("/^(=ybegin.*=yend[^$]*)$/ims", $string, $encoded); $encoded = $encoded[1]; // Extract the file size from the header. preg_match("/^=ybegin.*size=([^ $]+)/im", $encoded, $header); $headersize = $header[1]; // Extract the file name from the header. preg_match("/^=ybegin.*name=([^\\r\\n]+)/im", $encoded, $header); $filename = trim($header[1]); // Extract the file size from the trailer. preg_match("/^=yend.*size=([^ $\\r\\n]+)/im", $encoded, $trailer); $trailersize = $trailer[1]; // Extract the CRC32 checksum from the trailer (if any). preg_match("/^=yend.*crc32=([^ $\\r\\n]+)/im", $encoded, $trailer); $crc = @trim(@$trailer[1]); // Remove the header and trailer from the string before parsing it. $encoded = preg_replace("/(^=ybegin.*\\r\\n)/im", "", $encoded, 1); $encoded = preg_replace("/(^=yend.*)/im", "", $encoded, 1); // Remove linebreaks from the string. $encoded = trim(str_replace("\r\n", "", $encoded)); // Make sure the header and trailer filesizes match up. if ($headersize != $trailersize) { $this->error = "Header and trailer file sizes do not match. This is a violation of the yEnc specification."; return false; } // Decode $decoded = ''; for( $i = 0; $i < strlen($encoded); $i++) { if ($encoded{$i} == "=") { $i++; $decoded .= chr((ord($encoded{$i}) - 64) - 42); } else { $decoded .= chr(ord($encoded{$i}) - 42); } } // Make sure the decoded filesize is the same as the size specified in the header. if (strlen($decoded) != $headersize) { $this->error = "Header file size and actual file size do not match. The file is probably corrupt."; return false; } // Check the CRC value if ($crc != "" && strtolower($crc) != strtolower(sprintf("%04X", crc32($decoded)))) { $this->error = "CRC32 checksums do not match. The file is probably corrupt."; return false; } // Should we write to a file or spit back a string? if ($destination == "") { // Spit back a string. return $decoded; } else { // Make sure the destination directory exists. if (!is_dir($destination)) { $this->error = "Destination directory ($destination) does not exist."; return false; } // Write the file. // TODO: Replace invalid characters in $filename with underscores. $fp = @fopen("$destination/$filename", "wb"); if ($fp) { fwrite($fp, $decoded); fclose($fp); return true; } else { $this->error = "Could not open $destination/$filename for write access."; return false; } } } /** * yEncodes a file and returns it as a string. * * @param string $filename Full path and filename of the file to be encoded. * This can also be a URL (http:// or ftp://). * @param int $linelen Line length to use (can be up to 254 characters). * @param bool $crc32 Set to true to include a CRC checksum in the * trailer to allow decoders to verify data integrity. * * @return string|bool yEncoded file, or false on error. * * @see decodeFile() */ function encodeFile($filename, $linelen = 128, $crc32 = true) { // Read the file into memory. $fp = @fopen($filename, "rb"); if ($fp) { $file = ''; while (!feof($fp)) $file .= fread($fp, 8192); fclose($fp); // Encode the file. return $this->encode($file, $filename, $linelen, $crc32); } else { $this->error = "Could not open $filename for read access."; return false; } } /** * yDecodes an encoded file and writes the decoded file to the * specified directory, or returns it as a string if no directory is * specified. * * @param string $filename Full path and filename of the file to be decoded. * @param string $destination Destination directory where the decoded file will * be written. This must be a valid directory with no trailing * slash to which PHP has write access. If destination is * not specified, the decoded file will be returned rather than * written to the disk. * * @return bool|string If destination is not set, the decoded file will be * returned as a string. Otherwise, true will be returned on * success. In either case, false will be returned on error. * * @see encodeFile() */ function decodeFile($filename, $destination = "") { // Read the encoded file into memory. $fp = @fopen($filename, "rb"); if ($fp) { $infile = ''; while (!feof($fp)) $infile .= fread($fp, 8192); fclose($fp); // Send the file to the decoder. $out = $this->decode($infile, $destination); if ($out) { return $out; } else { // Decoding error. return false; } } else { $this->error = "Could not open $filename for read access."; return false; } } } /*========================================================================* * Documentation (there's no actual code below here) * *========================================================================*/ /** * @mainpage yEnc PHP Class * * @section intro Introduction * * yEnc is an informal standard for efficiently encoding binary files for * transmission on Usenet, in email, and in other similar mediums. It is * more efficient than other widely-used encoding methods, resulting in * smaller files (which in turn results in smaller downloads, which makes * people happy). * * This class implements a working yEnc encoder and decoder according * to the yEncode working draft specification as of version 1.3, which can * be found at http://www.yenc.org/yenc-draft.1.3.txt * * The only part of the yEnc spec that this class does not implement is * encoding and decoding of multipart yEncoded binaries. Support for this * may be added at a later date, but don't get your hopes up. * * @section limitations Limitations of PHP * * PHP is not an ideal language for implementing something like this. The * main issue is speed. The first thing you'll notice when you use the * class is that it is @em incredibly slow. Despite the fact that the * calculations involved are very simple, and as optimized as they can * possibly be, the problem is that there are just a @em lot of them, and * PHP is not a speedy language. * * If you want a fast yEnc implementation, you should use C. * * So why, then, did I write this class? I don't know really. I was bored. * It's entirely possible that it could come in handy for dealing with * very small binary files in a PHP-only environment. Mostly, I just like * toying with new concepts. * * @section support Support & Contact Info * * If you find a bug, or if you have a suggestion or comment, I'd love to * hear from you. If you need someone to hold your hand, please don't waste * my time. I went to a lot more trouble than I probably should have making * this class extremely easy to use, and I've also done my best to provide * thorough documentation, so stupid questions will very likely be met with * anger and profanity. * * That said, you can contact me via email at ryan\@wonko.com and you'll * always be able to find the latest version of this class and its * documentation at http://wonko.com/yenc/ * * @section license License & Copyright * * Copyright (c) 2002 Ryan Grove. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */