<?php /* * yEnc.php - yEnc PHP Class. * Copyright (c) 2003 Ryan Grove <ryan@wonko.com>. 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 March 31, 2003 * @version 1.1.0 */ class yenc { /** Text of the most recent error message (if any). */ var $error; /** * yEncodes a string and returns it. * * @param string String to encode. * @param filename Name to use as the filename in the yEnc header (this * does not have to be an actual file). * @param linelen Line length to use (can be up to 254 characters). * @param crc32 Set to <i>true</i> to include a CRC checksum in the * trailer to allow decoders to verify data integrity. * @return yEncoded string or <i>false</i> on error. * @see decode() */ function encode($string, $filename, $linelen = 128, $crc32 = true) { $encoded = ''; // 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; } // Encode each character of the string one at a time. $strLength = strlen($string); for( $i = 0; $i < $strLength; $i++) { // Escape special characters. switch($value = (ord($string{$i}) + 42) % 256) { case 0: // NULL case 9: // TAB case 10: // LF case 13: // CR case 32: // space case 46: // . case 61: // = $encoded .= '='.chr(($value + 64) % 256); break; default: $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 yEncoded string to decode. * @param destination Destination directory where the decoded file will * be written. This must be a valid directory <b>with no trailing * slash</b> to which PHP has write access. If <i>destination</i> is * not specified, the decoded file will be returned rather than * written to the disk. * @return If <i>destination</i> is not set, the decoded file will be * returned as a string. Otherwise, <i>true</i> will be returned on * success. In either case, <i>false</i> will be returned on error. * @see encode() */ function decode($string, $destination = "") { $encoded = array(); $header = array(); $trailer = array(); $decoded = ''; $c = ''; // 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 $strLength = strlen($encoded); for( $i = 0; $i < $strLength; $i++) { $c = $encoded{$i}; if ($c == '=') { $i++; $decoded .= chr((ord($encoded{$i}) - 64) - 42); } else { $decoded .= chr(ord($c) - 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. if ($fp = @fopen("$destination/$filename", "wb")) { 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 filename Full path and filename of the file to be encoded. * This can also be a URL (http:// or ftp://). * @param linelen Line length to use (can be up to 254 characters). * @param crc32 Set to <i>true</i> to include a CRC checksum in the * trailer to allow decoders to verify data integrity. * @return yEncoded file, or <i>false</i> on error. * @see decodeFile() */ function encodeFile($filename, $linelen = 128, $crc32 = true) { $file = ''; // Read the file into memory. if ($fp = @fopen($filename, "rb")) { 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 filename Full path and filename of the file to be decoded. * @param destination Destination directory where the decoded file will * be written. This must be a valid directory <b>with no trailing * slash</b> to which PHP has write access. If <i>destination</i> is * not specified, the decoded file will be returned rather than * written to the disk. * @return If <i>destination</i> is not set, the decoded file will be * returned as a string. Otherwise, <i>true</i> will be returned on * success. In either case, <i>false</i> will be returned on error. * @see encodeFile() */ function decodeFile($filename, $destination = "") { $infile = ''; // Read the encoded file into memory. if ($fp = @fopen($filename, "rb")) { while (!feof($fp)) $infile .= fread($fp, 8192); fclose($fp); // Send the file to the decoder. if ($out = $this->decode($infile, $destination)) { 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. * * @section license License & Copyright * * Copyright (c) 2003 Ryan Grove <ryan@wonko.com>. 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. */ ?>