<?php

/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4.0                                                      |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2001 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Mika Tuupola <tuupola@appelsiini.net>                       |
// +----------------------------------------------------------------------+
//
// $Id: Remote.php,v 1.3 2001/01/16 14:59:01 tuupola Exp $

define("GIF_SIGNATURE_LENGTH",      3);
define("GIF_VERSION_LENGTH",        3);
define("GIF_LSW_LENGTH",            2);
define("GIF_LSH_LENGTH",            2);

define("PNG_SIGNATURE_LENGTH",      8);
define("PNG_IHDR_LENGTH_LENGTH",    4);
define("PNG_IHDR_CHUNKTYPE_LENGTH", 4);
define("PNG_IHDR_IMW_LENGTH",       4);
define("PNG_IHDR_IMH_LENGTH",       4);

define("JPG_PARAM_LENGTH_LENGTH",   2);
define("JPG_DATA_PRECISION_LENGTH", 1);
define("JPG_IMW_LENGTH_LENGTH",     2);
define("JPG_IMH_LENGTH_LENGTH",     2);
define("JPG_NUM_COMPONENTS_LENGTH", 1);

// The following jpeg defines were taken from rdjpgcom.c, which is part
// of the Independent JPEG Group's software.
// Copyright (C) 1994-1997, Thomas G. Lane.
//
// ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz

define("M_SOF0",  0xC0);   /* Start Of Frame N */
define("M_SOF1",  0xC1);   /* N indicates which compression process */
define("M_SOF2",  0xC2);   /* Only SOF0-SOF2 are now in common use */
define("M_SOF3",  0xC3);
define("M_SOF5",  0xC5);   /* NB: codes C4 and CC are NOT SOF markers */
define("M_SOF6",  0xC6);
define("M_SOF7",  0xC7);
define("M_SOF9",  0xC9);
define("M_SOF10", 0xCA);
define("M_SOF11", 0xCB);
define("M_SOF13", 0xCD);
define("M_SOF14", 0xCE);
define("M_SOF15", 0xCF);
define("M_SOI",   0xD8);   /* Start Of Image (beginning of datastream) */
define("M_EOI",   0xD9);   /* End Of Image (end of datastream) */
define("M_SOS",   0xDA);   /* Start Of Scan (begins compressed data) */
define("M_APP0",  0xE0);   /* Application-specific marker, type N */
define("M_APP12", 0xEC);   /* (we don't bother to list all 16 APPn's) */
define("M_COM",   0xFE);   /* COMment */

class Image_Remote 
{
    var $url;         // the url we should be handling
    var $purl;        // parsed url
    var $filesize;    // filesize of the remote file
    var $mimetype;    // mimetype of the remote url
    var $imagetype;   // imagetype
    var $width;       // image width
    var $height;      // image height
    var $extra;       // some useless extrainfo
    var $errstr;      // error of the last method call

    /**
     * Image_Remote is a class to retrieve information on remote
     * imagefiles without actually downloading the whole image.
     * It mimics the GetImageSize() PHP function but works on 
     * remote images. Gif, jpeg and png currently supported.
     *
     * Usage:
     *
     *   $i = new Image_Remote("http://www.example.com/test.png");
     *   $data = $i->getImageSize();
     *
     * @param string $input URL of the remote image. Currently supports 
     *        only http.
     *
     * @author Mika Tuupola <tuupola@appelsiini.net>
     * @version $Revision: 1.3 $
     */

    function Image_Remote($input) 
    {
        $this->purl = parse_url($input);  

        if ($this->purl[scheme] == 'http') {
            $this->url = $input;

            // assign default port if not given
            if (!($this->purl[port])) {
                $this->purl[port] = 80;
            }
            $this->errstr = "";
            // actually fetch the info
            Image_Remote::_fetchImageInfo();

        } else {
            $this->errstr = "Only http supported.";
        }
    } // constructor

  
    /**
     * Gets information on the remote image. Imitates PHP's native 
     * GetImageSize. 
     * 
     * http://www.php.net/manual/function.getimagesize.php  
     *
     * @return array where array[0] contains the width of the image,
     *         array[1] contains the height of the image, array[2] contains
     *         an integer indicating type of the image. 1 = GIF, 2 = JPG, 
     *         3 = PNG. array[3] contains a string in WIDTH="X" HEIGHT="X" 
     *         format.
     */
    function getImageSize() 
    {
        $retval = array(0,0,0,"");

        $retval[0] = $this->width;  
        $retval[1] = $this->height;  
        $retval[2] = $this->imagetype;
        $retval[3] = "WIDTH=\"$retval[0]\" HEIGHT=\"$retval[1]\"";

        return($retval);   
    }

    /**
     * Gets the URL of the remote image. This is actually the same URL
     * which was given to the constructor. However it sometimes comes
     * handy to be able to retrieve the URL again from the object.
     * 
     * @return string URL
     */
    function getImageURL() 
    {
        $retval = $this->url;
        return($retval);
    }

    function _fetchImageInfo() 
    {

        // default to success.
        $retval = 1;
        // Open the connection and get the headers
        $fp = fsockopen ($this->purl[host], $this->purl[port], &$errno, &$errstr, 30);
        if (!$fp) {
            $this->errstr = "Could not open socket $errstr ($errno)\n";
            $retval = 0;
        } else {

            // get the headers.
            fputs ($fp, "GET " . $this->purl[path] . $this->purl[query] . " HTTP/1.1\r\n");
            fputs ($fp, "Host: " . $this->purl[host] . "\r\n");
            fputs ($fp, "User-Agent: Image_Remote PHP Class\r\n");
            fputs ($fp, "Connection: close\r\n\r\n");
            $line = trim(fgets($fp,128));
            while ($line) {
                // Extract redirection
                if (preg_match("/^HTTP\/1\.[0-1] (3[0-9][0-9] .*)/", $line, $matches)) {
                    $this->errstr = $matches[1] . " not implemented yet!";
                    $retval = 0;
                }
                // Extract client error
                    if (preg_match("/^HTTP\/1\.[0-1] (4[0-9][0-9] .*)/", $line, $matches)) {
                    $this->errstr = $matches[1];
                    $retval = 0;
                }
                // Extract server error
                if (preg_match("/^HTTP\/1\.[0-1] (5[0-9][0-9] .*)/", $line, $matches)) {
                    $this->errstr = $matches[1];
                    $retval = 0;
                }
                // Extract mime type
                if (preg_match("/Content-Type: (.*)/", $line, $matches)) {
                    $tempmime = chop($matches[1]);
                }
                // Extract filesize
                 if (preg_match("/Content-Length: ([0-9]*)/", $line, $matches)) {
                   $tempfilesize = $matches[1];
                }
                $line = trim(fgets($fp,128));
            }

            // If we got correct mimetype etc (we trust what the webserver says), 
            // continue to fetch the actual data. 
      
            // if no error yet
            if ($retval) {
            // if correct mimetype
                if (preg_match("/image\/(gif|jpeg|x-jpeg|x-png|png)/", $tempmime)) {
                    $this->mimetype = $tempmime;
                    $this->filesize = $tempfilesize;

                    SWITCH ($tempmime) {
                        case 'image/gif':
                            Image_Remote::_fetchAndParseGIFHeader($fp);
                            $this->imagetype = 1;
                            break;
                        case 'image/png':
                        case 'image/x-png':
                            Image_Remote::_fetchAndParsePNGHeader($fp);
                            $this->imagetype = 3;
                            break;
                        case 'image/jpeg':
                        case 'image/x-jpeg':
                            Image_Remote::_fetchAndParseJPGHeader($fp);
                            $this->imagetype = 2;
                            break;
                    }

                } else {
                    $this->errstr = "Unsupported mimetype $tempmime.";
                    $retval = 0;
                }
            } 
            fclose($fp);
        }
        return($retval);
    } // _fetchImageInfo


    function _fetchAndParseGIFHeader($fd) 
    {
        if (!$fd) {
            $this->errstr = "No socket.";
        } else {
            $signature = fread($fd, GIF_SIGNATURE_LENGTH);
            $version   = fread($fd, GIF_VERSION_LENGTH);
            $wbytes    = fread($fd, GIF_LSW_LENGTH);
            $hbytes    = fread($fd, GIF_LSH_LENGTH);
 
            // Host Byte Order
            $this->width  = Image_Remote::_htodecs($wbytes);
            $this->height = Image_Remote::_htodecs($hbytes);

            $this->extra  = $signature . $version;	
        }
    }

    function _fetchAndParsePNGHeader($fd) 
    {

        if (!$fd) {
            $this->errstr = "No socket.";
        } else {
            $signature = fread($fd, PNG_SIGNATURE_LENGTH);
            $ihdrl     = fread($fd, PNG_IHDR_LENGTH_LENGTH);
            $ihdrct    = fread($fd, PNG_IHDR_CHUNKTYPE_LENGTH);
            $wbytes    = fread($fd, PNG_IHDR_IMW_LENGTH);
            $hbytes    = fread($fd, PNG_IHDR_IMH_LENGTH);
 
            // Network Byte Order
            $this->width  = Image_Remote::_ntodecl($wbytes);
            $this->height = Image_Remote::_ntodecl($hbytes);

            $this->extra  = $signature;       
        }
    }

    // The jpeg parser is basically port of code found from rdjpgcom.c, 
    // which is  part of the Independent JPEG Group's software.
    // Copyright (C) 1994-1997, Thomas G. Lane.
    //
    // ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz
    //
    // Similar port can be found in elqGetImageFormat() by Oyvind Hallsteinsen 
    //
    // ftp://ftp.elq.org/pub/php3/elqGetImageFormat/elqGetImageFormat.php3

    function _fetchAndParseJPGHeader($fd) 
    {

        if (!$fd) {
            $this->errstr = "No socket.";
        } else {
            $done = 0;

            // first marker
            $c1 = ord(fread($fd, 1));
            $c2 = ord(fread($fd, 1));
            if ($c1 != 0xFF || $c2 != M_SOI) {
                $this->errstr="Not a jpeg file?";
            } else { 

                while (!($done)) {
        
                    // find next marker
                    $marker = ord(fread($fd, 1));
                    while ($marker != 0xFF) {
                        $marker = ord(fread($fd, 1));
                    }   
                    do {
                        $marker = ord(fread($fd, 1));
                    } while ($marker == 0xFF);

                    SWITCH ($marker) {
                        case M_SOF0:
                        case M_SOF1:
                        case M_SOF2:
                        case M_SOF3:
                        case M_SOF5:
                        case M_SOF6:
                        case M_SOF7:
                        case M_SOF9: 
                        case M_SOF10:
                        case M_SOF11:
                        case M_SOF13:
                        case M_SOF14:
                        case M_SOF15:
                            $length    = fread($fd, JPG_PARAM_LENGTH_LENGTH);
                            $precision = fread($fd, JPG_DATA_PRECISION_LENGTH);
                            $hbytes    = fread($fd, JPG_IMH_LENGTH_LENGTH);
                            $wbytes    = fread($fd, JPG_IMW_LENGTH_LENGTH);
                            $components= fread($fd, JPG_NUM_COMPONENTS_LENGTH);
               
                            // Network Byte Order
                            $this->width  = Image_Remote::_ntodecs($wbytes);
                            $this->height = Image_Remote::_ntodecs($hbytes);
                         
                            $this->extra  = "";
                            $done = 1;
                            break; 
                        case M_SOS:  
                            $done = 1; 
                            break;

                        // By default we skip over unwanted markers and avoid
                        // being fooled by 0xFF bytes in parameter segment.
                        default:
                            $lbytes = fread($fd, JPG_PARAM_LENGTH_LENGTH);
                            $length = Image_Remote::_ntodecs($lbytes); 
                            if ($length < JPG_PARAM_LENGTH_LENGTH) {
                                $this->errstr="Erronous parameter length.";
                                $done = 1;
                            } 
                            // the length is included in param length and
                            // allready read
                            $length -= JPG_PARAM_LENGTH_LENGTH;
                            fread($fd, $length);
                            break;
                    }    
                }
            }
        }
    }

    function _htodecl($bytes) 
    {
        $b1 = ord($bytes[0]);
        $b2 = (ord($bytes[1])<<8);
        $b3 = (ord($bytes[2])<<16);
        $b4 = (ord($bytes[3])<<24);
        return($b1 + $b2 + $b3 + $b4);
    }

    function _htodecs($bytes) 
    {
        $b1 = ord($bytes[0]);
        $b2 = (ord($bytes[1]) << 8);
        return($b1 + $b2);
    }

    function _ntodecl($bytes) 
    {
        $b1 = ord($bytes[3]);
        $b2 = (ord($bytes[2]) << 8);
        $b3 = (ord($bytes[1]) << 16);
        $b4 = (ord($bytes[0]) << 24);
        return($b1 + $b2 + $b3 + $b4); 
    }

    function _ntodecs($bytes) 
    {
        $b1 = ord($bytes[1]);
        $b2 = (ord($bytes[0]) << 8 );
        return($b1 + $b2);
    }

} // class 

?>
