<?php
/**
 * yEnc permite codificar y decodificar cadenas siguiendo el formato establecido en la página oficial: http://www.yenc.org/yenc-draft.1.3.txt
 *
 * @author     Sinfocol
 * @copyright  2009 www.sinfocol.org
 * @version    Release: 1.0.0.0
 * @link       http://www.sinfocol.org/2009/07/yenc-yencoder-y-ydecoder-online/
 * @since      Class available since Release 1.0.0.0
 */
class Yenc{
    const 
ERR_MALFORMED 'La cadena no parece estar codificada con yEnc.';
    const 
ERR_NOBEGIN   'La palabra clave "=ybegin" no se encuentra en la cabecera del archivo/cadena.';
    const 
ERR_NOLINE    'La palabra clave "line=#" no se encuentra en la cabecera del archivo/cadena.';
    const 
ERR_NOSIZE    'La palabra clave "size=#" no se encuentra en la cabecera del archivo/cadena.';
    const 
ERR_NONAME    'La palabra clave "name=a-z" no se encuentra la cabecera del archivo/cadena.';
    const 
ERR_NOEND     'La palabra clave "=yend" no se encuentra en el pie del archivo/cadena.';
    const 
ERR_WRONGLINE 'La palabra clave "line" es erronea, se debe a que una linea tiene mas caracteres que lo indicado.';
    const 
ERR_WRONGSIZE 'La palabra clave "size" es erronea, la cadena decodificada tiene una longitud diferente a la indicada.';
    const 
ERR_WRONGCRC32'Error de comprobacion del codigo de redundancia ciclica. Para evitar este error elimine la palabra clave "crc32" del archivo/cadena.';

    
/**
     * Codifica una cadena o archivo usando el algoritmo propuesto Jürgen Helbing
     * @param string $nombre El nombre del archivo
     * @param string $bytes El mensaje que se codificará
     * @param int $caracteresPorLinea Carácteres por línea en el codificado
     * @return string Cadena codificada con el formato yEnc
     */
    
public function encode($nombre$bytes$caracteresPorLinea 128){
        
$strlen strlen($bytes);
        
$i 0;
        
$acum 0;
        
$codificado '';
        while(
$i $strlen){
            
$chr   '';
            
//Obtenemos el ascii de cada byte del mensaje y hacemos la operación
            
$ascii = (ord($bytes[$i++]) + 42) % 256;
            switch(
$ascii){
                case 
0:  // \x00 = Bytes nulos
                
case 9:  // \x09 = Tabulaciones
                
case 10// \x0a = Salto de línea
                
case 13// \x0d = Retorno de carro
                
case 32// \x20 = Espacio
                
case 46// \x2e = Punto
                
case 61// \x3d = Igual
                    //Estos carácteres se escapan
                    
$ascii = ($ascii 64);
                    
$chr   '=' chr($ascii);
                    
$acum += 2;
                    break;
                default:
                    
$chr   chr($ascii);
                    
$acum++;
                    break;
            }
            
//Partimos el mensaje con un salto de línea según los carácteres por línea
            
if($acum >= $caracteresPorLinea){
                
$chr .= "\n";
                
$acum 0;
            }
            
//Vamos concatenando los resultados
            
$codificado .= $chr;
        }
        
//Se calcula el CRC32 del mensaje original en forma hexadecimal FFFFFFFF
        
$crc32 strtoupper(base_convert(sprintf('%u'crc32($bytes)), 1016));
        
//La cabecera tal cual está especificada en el documento oficial
        
$yenc  "=ybegin line=$caracteresPorLinea size=$strlen name=$nombre\n";
        
$yenc .= trim($codificado) . "\n";
        
//El pie del archivo con el CRC32
        
$yenc .= "=yend size=$strlen crc32=$crc32";
        return 
$yenc;
    }

    
/**
     * Decodifica una cadena que fue codificada con yEncoder
     * @param string $bytes Es el mensaje codificado
     * @return string El texto en plano
     */
    
public function decode($bytes){
        
//Removemos los trailing
        
$bytes trim($bytes);
        
//Reemplazamos los finales de línea de windows por los de unix
        
$bytes str_replace("\r\n""\n"$bytes);
        
//Dividimos el mensaje limitado por los finales de línea
        
$bytes explode("\n"$bytes);

        
//Recorremos cada línea
        
for($i=0$i<count($bytes); $i++){
            if((
$pos strpos($bytes[$i], '=ybegin')) === false){
                
//Si la línea no contiene la cadena "=ybegin" entonces la eliminamos
                
array_splice($bytes$i1);
                
$i--;
            }else{
                
//Si la encuentra hacemos que la primera cadena en esa línea sea "=ybegin"
                
$bytes[0] = substr($bytes[0], $pos);
                break;
            }
        }

        
//Si sólo encuentra una o menos líneas, hay error
        
if(count($bytes) < 2) return self::ERR_MALFORMED;

        
//La cabecera es el primer elemento del array
        
$header array_shift($bytes);
        
//El pie del archivo es el último elemento del array
        
$footer array_pop($bytes);
        
//En este momento $bytes contiene únicamente el mensaje a decodificar

        //Chequeo general de los parámetros
        
if(substr($header07) != '=ybegin')
            return 
self::ERR_NOBEGIN;

        if(! (
$line $this->_getParam('line'$header)) )
            return 
self::ERR_NOLINE;

        if(! (
$size $this->_getParam('size'$header)) )
            return 
self::ERR_NOSIZE;

        if(! (
$name $this->_getParam('name'$header)) )
            return 
self::ERR_NONAME;

        if(
substr($footer05) != '=yend')
            return 
self::ERR_NOEND;

        
$crc32 $this->_getParam('crc32'$footer);

        if(
strlen($bytes[0]) > $line)
            return 
self::ERR_WRONGLINE;

        
$name   str_replace('"'''$name);
        
$bytes  str_replace("\n"''implode($bytes));
        
$strlen strlen($bytes);
        
$i 0;
        
$decodificado '';
        
$especial false;
        while(
$i $strlen){
            
$chr   '';
            
//Obtenemos el ascii de los bytes del mensaje a decodificar
            
$ascii ord($bytes[$i++]);
            if(
$ascii != 61){
                
//Se realiza la respectiva operación inversa, controlando los carácteres especiales con la variable $especial
                
$ascii $especial$ascii-42-64$ascii-42;
                if(
$ascii 0$ascii += 256;
                
$chr   chr($ascii);
                
$especial false;
            }else{
                
$especial true;
            }
            
$decodificado .= $chr;
        }

        if(
$crc32 != false){
            
//Se comprueba la integridad de los datos
            
$crc32decodificado strtoupper(base_convert(sprintf('%u'crc32($decodificado)), 1016));
            if(
strtoupper($crc32) != $crc32decodificado)
                return 
self::ERR_WRONGCRC32;
        }
        return 
$decodificado;
    }

    
/**
     * Extrae los parámetros de la cabecera o pie del mensaje o archivo codificado
     * @param string $nombre Nombre del parámetro
     * @param string $linea Línea sobre la cual se quiere extraer los parámetros
     * @return mixed Valor del parámetro, en caso de no encontrar ningún parámetro retorna falso
     */
    
private function _getParam($nombre$linea){
        
$elem explode(' '$linea);
        foreach(
$elem as $p){
            list(
$param$val) = explode('='$p2);
            if(
$nombre == strtolower($param)) return $val;
        }
        return 
false;
    }
}