<?php

/* 
    Kismet to kml (Google Earth) log file converter alias "Kismet Earth"
    Released by Philippe Niquille, philippe at niquille dot com, www.niquille.com
    
    Feel free to use my script and add enhancements. Please drop me an e-mail if you have any code enchancements, suggestions or whatever. Usually I'm a friendly guy ;-).
        
    ******************************
    *   version 0.2  - 2.5.2006
    ******************************
        
    features
    --------
    parses Kismet log files (.xml and .gps) to generate a google earth kml file
    - draws WarDrive Path/Road driven as tesselated line
    - adds discovered access points to specific location on the map, parses meta information (mac, channel, packets, clients, etc.)
    - draws 3D polygons instead of simple AP images. The shape should represent the signal area as far as every corner has been
      captured correctly. The height depends on the channel. The color is yellow (open), green (closed) or blue when open and cloaked.
    - parses ap_manuf and client_manuf for vendor information
    - saves .kml as .kmz (zipped) upon request
    
    requirements
    ------------
    PHP5 because of simple_xml, file_get_content functions
    
    usage
    -----
    Use it as command line script as followed:
    /usr/bin/php -f parse.php <Kismet-Sep-16-2005-5> <kmz>
    
    The first argument is the kismet log file name without extension. The second one is optional.
        
    changelog
    ---------
    v0.2       - added Road Driven functionality and Road Section (as contributed by john.litchfield [at] gmail.com
               - changed Icons, now only internal icons are used
               - changed the visible structure
               - fixed time issue
    v0.1 beta3 - added pack as kmz functionality (zip compression)
    v0.1 beta2 - replaced real slow for loop with foreach and gps coordinates index array, huge speed boost
               - added vendor information parser, added to description
               - tweaked description section
    v0.1 beta1 - initial release
        
    todo/wishlist
    -------------
    - add better error correction for paths, polygons
    - parse default password list
    - give the WarSession a name
    - merge multiple log files
    - do dynamic (network connection) stuff

*/

/*******************
*   set vars
********************/
$ap_manuf_file "ap_manuf";
$client_manuf_file "client_manuf";

//$argv[1] = "Kismet-Dec-16-2005-1"; //debug
//$argv[2] = "kmz";

/*******************
*   execution time
********************/
$mtime explode(" ",microtime());
$mtime $mtime[1] + $mtime[0];
$starttime $mtime;

/*******************
*   open files
********************/
if(isset($argv[1]))//get filenames
{
    
$xml_file $argv[1].'.xml';
    
$gps_file $argv[1].'.gps';
}else
    exit(
"No argument specified\n");
    
if(
file_exists($xml_file) && file_exists($gps_file))
{
    
$xml simplexml_load_file($xml_file); //load kismet log
    
$gps simplexml_load_file($gps_file); //load corresponding detailed gps log
}else
    exit(
"Files couldn't be opened: ".$xml_file.", ".$gps_file."\n");

if(
file_exists($ap_manuf_file) && file_exists($client_manuf_file))
{
    
$ap_manuf parse_vendor($ap_manuf_file); //load access point vendor data
    
$client_manuf parse_vendor($client_manuf_file); //load client vendor data
}else
    exit(
"Files couldn't be opened: ".$ap_manuf_file.", ".$client_manuf_file."\n");

$array object2array($xml); //convert simplexml object to php array, easier to work on..

/*******************
*   do meta stuff
********************/
$meta['session-duration'] = strtotime($xml['end-time'])-strtotime($xml['start-time']); //get duration (unix timestamp, in seconds)
$meta['session-duration'] = gmdate("H:i:s",$meta['session-duration']);

$meta['net-count'] = count($array['wireless-network']);
$meta['parsed'] = $meta['net-count'];

/*******************
*   parse .gps file and build gps coordinates index
********************/
foreach($gps->{'gps-point'} as $value)
{
    
$coordinates[(string) $value['bssid']][] = array((string) $value['lon'], (string) $value['lat']);
}

/*******************
*   loop trough kismet wireless nets - types: infrastructure, probe
********************/
foreach($array['wireless-network'] as $key => $network)
{
    if(
    (
$network['gps-info']['min-lat'] == '90.000000' && $network['gps-info']['min-lon'] == '180.000000'//remove nets with no gps coordinates, no sense to display them on a map
    
|| $network['BSSID'] == '00:00:00:00:00:00'
    
|| $xml->{'wireless-network'}[$key]['type'] != 'infrastructure'//remove zero bssid nets, just junk
    
{
        if(
$xml->{'wireless-network'}[$key]['type'] == 'probe'//count probes
            
$meta['probe-count']++;
        
        
$meta['net-count']--; //remove non infrastructure from total net count, exit foreach
        
continue;
       
    }elseif(!isset(
$lookatnet)) //get the first usable net to set LookAt coordinates
        
$lookatnet $key;
    
    if(empty(
$network['SSID'])) $network_name "no ssid";
    else 
$network_name $network['SSID'];
    
    
$kml .= "<Folder>\n<name><![CDATA[".$network_name."]]></name>\n";

    
$kml .= "<Placemark>\n<name><![CDATA[".$network_name."]]></name>\n";
    
    
/*******************
    *   build description html
    ********************/
    
$seenfor strtotime($xml->{'wireless-network'}[$key]['last-time'])-strtotime($xml->{'wireless-network'}[$key]['first-time']);
    
    
$kml .= "\t<description><![CDATA[BSSID: ".$network['BSSID']."<br>seen for ".floor($seenfor/60)." min (".floor($seenfor)." sec)<br>"//get duration
    //$kml .= "first seen: ".$xml->{'wireless-network'}[$key]['first-time'];
    //$kml .= "<br>last seen: ".$xml->{'wireless-network'}[$key]['last-time']."<br><hr>\n";
    
$kml .= "BSSID: ".$network['BSSID']."<br>\nchannel: ".$network['channel']."<br>\n";
  
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//net encrypton on?
    
{
        if(
$network['encryption'] == 'WEP')
            
$kml .= "<font color=\"green\">encryption: WEP</font>";
            
        elseif(
is_array($network['encryption']))
        {
            
$kml .= "<font color=\"green\">encryption: ";

            foreach(
$network['encryption'] as $enc)
            {
               
$kml .= $enc." ";
            }
            
$kml .= "</font>";
        }
    }else
        
$kml .= "<font color=\"red\">no encryption</font>";
    
    
$kml .= "<br>\ncloaked: ".$xml->{'wireless-network'}['cloaked'];
    
$kml .= "<br>\nmax rate: ".$xml->{'wireless-network'}['maxrate'];
    
    if(isset(
$network['ip-address']))
        
$kml .= "<br>IP range: ".$network['ip-address']['ip-range'];
        
     
/*******************
    *   get vendor info
    ********************/
    
if(get_manuf((string) $network['BSSID'],$ap_manuf) != false)
    {
        
$manuf get_manuf((string) $network['BSSID'],$ap_manuf);
        
$kml .= "<hr>\n<b>Vendor Info</b><br>Type: ".$manuf[0]." ".$manuf[1];
        if(!empty(
$manuf[2]))
            
$kml .= "<br>default SSID: ".$manuf[2].", channel ".$manuf[3];
        if(!empty(
$manuf[4]))
            
$kml .= "<br>default ip range: ".$manuf[4];
    }
    
    
$kml .= "<hr>\n<b>GPS coordinates</b><br>\nfirst-seen: ".$network['gps-info']['min-lat']." lat, ".$network['gps-info']['min-lon']." lon, ".round($network['gps-info']['min-alt'],1)." alt";
    
$kml .= "\n<br>last-seen: ".$network['gps-info']['max-lat']." lat, ".$network['gps-info']['max-lon']." lon, ".round($network['gps-info']['max-alt'],1)." alt";
    
$kml .= "<hr>\n<b>captured packets</b><br>\n";
    
    
/*******************
    *   list the packets section (IV's, LLC, Data, etc.)
    ********************/
    
foreach($network['packets'] as $tag => $value)
    {
        
$kml .= $tag.": ".$value."<br>\n";
    }
    
$kml .= "total datasize captured: ".$network['datasize']."\n";
    
    
/*******************
    *   parse clients, if existant - types: fromds, tods
    ********************/
    
if(isset($network['wireless-client']))
    {
        
$kml .="<hr>\n<b>captured ".count($network['wireless-client'])." attached clients</b>\n<br>\n<ul>\n";
        
        foreach(
$network['wireless-client'] as $client//list all attached and captured clients
        
{
            
$kml .= "<li>MAC: ".$client['client-mac'];
            
            if(
get_manuf((string) $client['client-mac'],$client_manuf) != false)
            {
                
$manuf get_manuf((string) $client['client-mac'],$client_manuf);
                
$kml .= " (".$manuf[0]." ".$manuf[1].")";
            }
        
            
$kml .= "<br>IP: ";
            if(isset(
$client['client-ip-address'])) //IP discovered?
                
$kml .= $client['client-ip-address'];
            else
               
$kml .= "none discovered";
           
$kml .= "</li>\n";
        }
    }
    
    
$kml .= "</ul>]]>\n</description>\n\t<View>\n\t<longitude>".$network['gps-info']['min-lon']."</longitude>\n";
    
$kml .= "\t<latitude>".$network['gps-info']['min-lat']."</latitude>\n</View>\n";
    
    
$kml .= "\t<visibility>1</visibility>\n\t<styleUrl>root://styleMaps#default?iconId=0x307</styleUrl>\n";
    
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//net encrypton on?
    
{
    
/*
        if($argv[2] == 'kmz') //if kmz requested point to local icons
            $kml .= "\t<Style>\t<icon>node_closed.png</icon></Style>";
        else
            $kml .= "\t<Style>\t<icon>http://www.niquille.com/wp-content/node_closed.png</icon></Style>";
    */
        
$kml .= "\t<Style>\t<IconStyle>\n<scale>.9</scale>\n<Icon><href>root://icons/palette-2.png</href>\n<x>160</x>\n<y>224</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>";
        
$meta['wep-count']++;
    }else{
    
/*    if($argv[2] == 'kmz')
            $kml .= "\t<Style>\t<icon>node_open.png</icon></Style>";
        else
            $kml .= "\t<Style>\t<icon>http://www.niquille.com/wp-content/node_open.png</icon></Style>";
    */
        
$kml .= "\t<Style>\t<IconStyle>\n<scale>.9</scale>\n<Icon><href>root://icons/palette-3.png</href>\n<x>64</x>\n<y>96</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>";
    }
    
    
$kml .= "\t<Point>\n\t<coordinates>".$network['gps-info']['min-lon'].", ".$network['gps-info']['min-lat'].", ".$network['gps-info']['min-alt']."</coordinates>\n</Point>\n</Placemark>\n";
    
    
/*******************
    *   add gps coordinates to path source
    ********************/
    
$line .= $network['gps-info']['min-lon'].", ".$network['gps-info']['min-lat'].", ".$network['gps-info']['min-alt']." ";
    
    
/*******************
    *   assemble polygon placemark
    ********************/
    
if($network['channel'] != '0'//we don't display networks with no height
    
{
    
    
$poly "<Placemark>\n<name><![CDATA[3D area]]></name>\n<visibility>0</visibility>\n<open>0</open>\n";
    
$poly .= "<Style>\n\t<IconStyle>\n<scale>.65</scale>\n<Icon><href>root://icons/palette-3.png</href>\n<x>96</x>\n<y>160</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle>";
    
$poly .= "<LineStyle>\n\t<width>1.5</width></LineStyle>\n\t<PolyStyle>";
    
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//set color 
        
$poly .= "<color>8f00ff00</color>\n"//green, closed
    
elseif($meta[$key]['cloaked'] == 'true')
        
$poly .= "<color>7dff0000</color>\n"//blue, cloaked
    
else
        
$poly .= "<color>7d00ffff</color>\n"//yellow, open
    
    
$poly .= "</PolyStyle>\n</Style>\n<Polygon>\n<extrude>1</extrude>\n<tessellate>0</tessellate>\n";
    
$poly .= "<altitudeMode>relativeToGround</altitudeMode>\n<outerBoundaryIs>\n<LinearRing>\n<extrude>0</extrude>";
    
$poly .= "<tessellate>0</tessellate>\n<altitudeMode>clampToGround</altitudeMode>\n<coordinates>";

    
$poly_alt $network['channel']*10//the polygon height multiplied by 10 to give a reasonable height
    
    
foreach($coordinates[$network['BSSID']] as $index => $gpoints)
    {
        if(
$gpoints[0] != $coordinates[$network['BSSID']][$index-1][0]
           && 
$gpoints[1] != $coordinates[$network['BSSID']][$index-1][1]) //remove duplicates
                
$poly .= $gpoints[0].", ".$gpoints[1].", ".$poly_alt." ";
    }
    
    
$poly .= $coordinates[$network['BSSID']][0][0].", ".$coordinates[$network['BSSID']][0][1].", ".$poly_alt." "//finish polygon at first coordinate
    
$poly .= "</coordinates>\n</LinearRing>\n</outerBoundaryIs>\n</Polygon>\n</Placemark>";
    
    
/*******************
    *   assemble the access point route section (show for how long the access point was visible on the driven route) - Addition by john.litchfield [at] gmail.com
    *   note: to navigate on the google earth icon plaette keep this in mind: upper right corner = 224, 224 - then just substract 32 either on x or/and y and you get the icons next to it
    ********************/
    
$route_section "<Placemark>\n<name><![CDATA[Route section]]></name>\n<visibility>0</visibility>\n";
    
$route_section .= "<Style><LineStyle>\n<color>ff0000ff</color>\n\t<width>2</width></LineStyle></Style>\n";
    
$route_section .= "<Style><IconStyle>\n<Icon><href>root://icons/palette-5.png</href>\n<x>160</x>\n<y>224</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>\n";
    
$route_section .= "<MultiGeometry><LineString><coordinates>";

    
$kml_alt 0;

    foreach(
$coordinates[$network['BSSID']] as $index => $gpoints)
    {
        if(
$gpoints[0] != $coordinates[$network['BSSID']][$index-1][0]
        && 
$gpoints[1] != $coordinates[$network['BSSID']][$index-1][1]) //remove duplicates
                
$route_section .= $gpoints[0].", ".$gpoints[1].", ".$kml_alt." ";
        }
        
        
$route_section .= "</coordinates>\n</LineString>\n</MultiGeometry>\n</Placemark>\n";
    }
    
    
$kml .= $poly."\n".$route_section."\n</Folder>\n"//gather final SSID Folder with description, 3D polygon, route section
    
//end foreach



/*******************
*   assemble final KML xml file
********************/
$meta['wep-count'] = ($meta['wep-count']/$meta['net-count'])*100//encrypted percentage of total nets

$kml_final "<?xml version='1.0' encoding='UTF-8'?>\n<kml xmlns='http://earth.google.com/kml/2.0'>\n<Folder>\n<name>";
$kml_final .= $meta['session-duration'].', '.$meta['net-count'].' nets, '.$meta['probe-count'].' probes ('.$meta['parsed'].' total parsed) - '.date('d.m.Y',strtotime($xml['start-time']));
$kml_final .= "</name>\n<visibility>1</visibility><description>encrypted: ".round($meta['wep-count'],2)."%</description>\n";

//fly to first network - range: zoom level, tilt: view angle
$kml_final .= "<LookAt>\n\t<longitude>".$array['wireless-network'][$lookatnet]['gps-info']['min-lon']."</longitude>\n\t<latitude>".$array['wireless-network'][$lookatnet]['gps-info']['min-lat']."</latitude>";
$kml_final .= "<range>1000</range><tilt>54</tilt><heading>-35</heading></LookAt>";

/*******************
*   add a "Control" Folder
********************/

$kml_final .= "<Folder>\n<name>Overall GPS Drive</name>";
$kml_final .= "<open>1</open>\n";
$kml_final .= "<Placemark>\n<name>Route Driven</name>\n<visibility>1</visibility>"//Visibility 1 to show route driven

//green: 5a00ff00, orange: FF00A5FF, blue: 7dff0000
$kml_final .= "<Style><LineStyle>\n<color>5a00ff00</color>\n\t<width>6</width></LineStyle></Style>\n";
$kml_final .= "<Style><IconStyle>\n<Icon><href>root://icons/palette-2.png</href>\n<x>0</x>\n<y>128</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>\n";
$kml_final .= "<open>0</open>\n";
$kml_final .= "<MultiGeometry><LineString><coordinates>";

/*******************
*   Route driven - Addition by john.litchfield [at] gmail.com (it's different than my WarSession Path. He reads some sort of endpoints which sometimes seem more reliable.)
********************/
$endpoint_alt 50;

foreach(
$coordinates['GP:SD:TR:AC:KL:OG'] as $index => $gpoints)
{
    if(
$gpoints[0] != $coordinates['GP:SD:TR:AC:KL:OG'][$index-1][0]
    && 
$gpoints[1] != $coordinates['GP:SD:TR:AC:KL:OG'][$index-1][1]) //remove duplicates
    
{
        
$kml_final .= $gpoints[0].", ".$gpoints[1].", ".$kml_alt." ";
        
$endpoint $gpoints[0].", ".$gpoints[1].", ".$kml_alt." "//Setting a placeholder for the end of the drive
    
}
}
$kml_final .= "</coordinates>\n</LineString>\n</MultiGeometry>\n</Placemark>";

//Start Point
$kml_final .= "<Placemark>\n<name>Start</name><visibility>1</visibility>";
$kml_final .= "<Style><IconStyle>\n<scale>.65</scale>\n<Icon><href>root://icons/palette-5.png</href>\n<x>64</x>\n<y>128</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>\n<Point>\n<coordinates>\n";
$kml_final .= $coordinates['GP:SD:TR:AC:KL:OG'][0][0].", ".$coordinates['GP:SD:TR:AC:KL:OG'][0][1].", ".$kml_alt." "//Placeholder for beginning of drive
$kml_final .= "</coordinates>\n</Point>\n</Placemark>";

//End Point
$kml_final .= "<Placemark>\n<name>Finish</name><visibility>1</visibility>";
$kml_final .= "<Style><IconStyle>\n<scale>.65</scale>\n<Icon><href>root://icons/palette-5.png</href>\n<x>160</x>\n<y>0</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle></Style>\n";
$kml_final .= "<Point>\n<coordinates>";
$kml_final .= $endpoint."</coordinates></Point>\n</Placemark>\n";

// output the WarSession Path in a separate placemark
$kml_final .= "\n<Placemark>\n<name>WarSession Path</name>\n<description></description>\n<visibility>0</visibility>\n";
$kml_final .= "\t<Style>\n\t<geomColor>BB0000FF</geomColor>\n<geomScale>3</geomScale>\n";
$kml_final .= "<IconStyle>\n<scale>.65</scale>\n<Icon><href>root://icons/palette-5.png</href>\n<x>224</x>\n<y>192</y>\n<w>32</w>\n<h>32</h>\n</Icon></IconStyle>\n</Style>\n\t<LineString>";
$kml_final .= "\n\t<tessellate>1</tessellate>\n\t<coordinates>$line</coordinates>\n</LineString>\n</Placemark>\n"//tesselate adjusts to terrain

$kml_final .= "</Folder>";

$kml_final .= $kml;

$kml_final .= "</Folder>\n</kml>";

//$kml_final = simplexml_load_string($kml_final); //check kml syntax (xml), fails because of <br>, <li> in description tag
//$kml_final->asXML('new.kml');

writeXML($argv[1].".kml",$kml_final); //write final XML file as KML to disk
echo $argv[1].".kml saved.\n";

if(
$argv[2] == 'kmz'//output as kmz (zipped) if second command line argument supplied
{
    
$zip = new zipfile();
    
//$zip->addFiles(array($argv[1].".kml","node_open.png","node_closed.png")); //also include icons
    
$zip->addFiles(array($argv[1].".kml"));
    
$zip->output($argv[1].".kmz");
    echo 
$argv[1].".kmz saved.\n";
}

/*******************
*   execution time
********************/
$mtime explode(" ",microtime());
$mtime $mtime[1] + $mtime[0];
$endtime $mtime;
$totaltime = ($endtime $starttime);
echo 
"Parsed in ".round($totaltime,2)." seconds\n";

/*******************
*   functions
********************/
function parse_vendor($filename)
{
$array explode("\n",file_get_contents($filename));

foreach(
$array as $row)
{
    
$tabs explode("\t",$row);
    
$mac explode("/",$tabs[0]);
    
array_shift($tabs); //pop off first element (mac) 
    
    
if($mac[1] == "FF:FF:FF:FF:00:00")
        
$mac rtrim($mac[0],":00:00");
    elseif(
$mac[1] == "FF:FF:FF:00:00:00")
        
$mac rtrim($mac[0],":00:00:00").":*";
    
    
$macarray[(string) $mac] = $tabs;
    
//end foreach
return $macarray;
/*
get's array with all the different mac ranges and vendor/manufactor info:

Array ( [00:00:22:22] => 
    Array ( [0] => Lucent 
            [1] => Orinoco Silver 
            [2] => 
            [3] => 0 
            [4] => )
        etc.
*/
}

function 
get_manuf($mac,$macarray)
{
$longmac substr($mac,0,11); //format to array style
$shortmac substr($mac,0,9)."*";

if(isset(
$macarray[$longmac]))
    return 
$macarray[$longmac];
    
elseif(isset(
$macarray[$shortmac]))
    return 
$macarray[$shortmac];
    
else
    return 
false//no info found
}

function 
writeXML($filename$kml)
{
   
$handle fopen($filename,'w+'); //open xml file for writing
  // fwrite($handle, header("Content-type: text/xml")); //write header to xml file
   
fwrite($handle$kml);
   
fclose($handle);
}

/*******************
*   pulled from php.net
********************/
function object2array($object)
{
   
$return NULL;
      
   if(
is_array($object))
   {
       foreach(
$object as $key => $value)
           
$return[$key] = object2array($value);
   }
   else
   {
       
$var get_object_vars($object);
          
       if(
$var)
       {
           foreach(
$var as $key => $value)
               
$return[$key] = ($key && !$value) ? NULL object2array($value);
       }
       else return 
$object;
   }

   return 
$return;
}

/*
 * zipLib v1.1
 *
 * Last Modification and Extension By :
 *
 *  Hasin Hayder
 *  HomePage : www.hasinme.info
 *  Email : countdraculla@gmail.com
 *  IDE : PHP Designer 2005
 *
 *
 * Originally Based on :
 *
 *  http://www.zend.com/codex.php?id=535&single=1
 *  By Eric Mueller <eric@themepark.com>
 *
 *  http://www.zend.com/codex.php?id=470&single=1
 *  by Denis125 <webmaster@atlant.ru>
 *
 *  a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified
 *  date and time of the compressed file
*/

class zipfile
{
    
/**
     * Array to store compressed data
     *
     * @var  array    $datasec
     */
    
var $datasec      = array();

    
/**
     * Central directory
     *
     * @var  array    $ctrl_dir
     */
    
var $ctrl_dir     = array();

    
/**
     * End of central directory record
     *
     * @var  string   $eof_ctrl_dir
     */
    
var $eof_ctrl_dir "\x50\x4b\x05\x06\x00\x00\x00\x00";

    
/**
     * Last offset position
     *
     * @var  integer  $old_offset
     */
    
var $old_offset   0;


    
/**
     * Converts an Unix timestamp to a four byte DOS date and time format (date
     * in high two bytes, time in low two bytes allowing magnitude comparison).
     *
     * @param  integer  the current Unix timestamp
     *
     * @return integer  the current date in a four byte DOS format
     *
     * @access private
     */
    
function unix2DosTime($unixtime 0) {
        
$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);

        if (
$timearray['year'] < 1980) {
            
$timearray['year']    = 1980;
            
$timearray['mon']     = 1;
            
$timearray['mday']    = 1;
            
$timearray['hours']   = 0;
            
$timearray['minutes'] = 0;
            
$timearray['seconds'] = 0;
        } 
// end if

        
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
                (
$timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
    } 
// end of the 'unix2DosTime()' method


    /**
     * Adds "file" to archive
     *
     * @param  string   file contents
     * @param  string   name of the file in the archive (may contains the path)
     * @param  integer  the current timestamp
     *
     * @access public
     */
    
function addFile($data$name$time 0)
    {
        
$name     str_replace('\\''/'$name);

        
$dtime    dechex($this->unix2DosTime($time));
        
$hexdtime '\x' $dtime[6] . $dtime[7]
                  . 
'\x' $dtime[4] . $dtime[5]
                  . 
'\x' $dtime[2] . $dtime[3]
                  . 
'\x' $dtime[0] . $dtime[1];
        eval(
'$hexdtime = "' $hexdtime '";');

        
$fr   "\x50\x4b\x03\x04";
        
$fr   .= "\x14\x00";            // ver needed to extract
        
$fr   .= "\x00\x00";            // gen purpose bit flag
        
$fr   .= "\x08\x00";            // compression method
        
$fr   .= $hexdtime;             // last mod time and date

        // "local file header" segment
        
$unc_len strlen($data);
        
$crc     crc32($data);
        
$zdata   gzcompress($data);
        
$zdata   substr(substr($zdata0strlen($zdata) - 4), 2); // fix crc bug
        
$c_len   strlen($zdata);
        
$fr      .= pack('V'$crc);             // crc32
        
$fr      .= pack('V'$c_len);           // compressed filesize
        
$fr      .= pack('V'$unc_len);         // uncompressed filesize
        
$fr      .= pack('v'strlen($name));    // length of filename
        
$fr      .= pack('v'0);                // extra field length
        
$fr      .= $name;

        
// "file data" segment
        
$fr .= $zdata;

        
// "data descriptor" segment (optional but necessary if archive is not
        // served as file)
        
$fr .= pack('V'$crc);                 // crc32
        
$fr .= pack('V'$c_len);               // compressed filesize
        
$fr .= pack('V'$unc_len);             // uncompressed filesize

        // add this entry to array
        
$this -> datasec[] = $fr;

        
// now add to central directory record
        
$cdrec "\x50\x4b\x01\x02";
        
$cdrec .= "\x00\x00";                // version made by
        
$cdrec .= "\x14\x00";                // version needed to extract
        
$cdrec .= "\x00\x00";                // gen purpose bit flag
        
$cdrec .= "\x08\x00";                // compression method
        
$cdrec .= $hexdtime;                 // last mod time & date
        
$cdrec .= pack('V'$crc);           // crc32
        
$cdrec .= pack('V'$c_len