<?php
/***************************************************************************
* Copyright (C) 2007 by Cesar D. Rodas *
* hide@address.com *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
/**
* File Exchange Protocol
*
* This is a PHP class based on Amazon S3 like transference protocol.
* The Idea is to develope a fexible, plataform indepente, file transfer
* that works over HTTP(s). Basically for exchange files between servers.
*
* @package File Exchange Protocol
* @version 1.0
* @author Cesar D. Rodas <hide@address.com>
* @license BSD License
*/
require_once( dirname(__FILE__).'/FEP_base.php'); /* include base class*/
require_once( dirname(__FILE__).'/http.php'); /* include http class*/
define('read',1);
define('write',2);
define('append',3);
/**
* Cache object.
*
* For better performance, the cache file comunitacion (read-write)
* is handle by an intermediate class that is cache.
*
*
* @package File Exchange Protocol
* @version 1.0
* @author Cesar D. Rodas <hide@address.com>
* @license BSD License
*/
class httpCacheObject {
/**
* True if must need to be sync to the server
* @var bool
* @access public
*/
var $dirty;
/**
* Size of the cache
* @var integer
* @access public
*/
var $size;
/**
* Value of the cache
* @var string
* @access public
*/
var $value;
}
/**
* Cliente Class
*
* @package File Exchange Protocol
* @version 1.0
* @author Cesar D. Rodas <hide@address.com>
* @license BSD License
*/
class FEP_client extends FEP_base {
/**
* HTTP Client object
* @var object
* @acces private
*/
var $http;
/**
* Opened Mode
*
* Save in what method is opened a file.
* @var int
* @access private
*/
var $mode;
/**
* Server URL
* @var string
* @access private
*/
var $server;
/**
* File (for operation) Path
* @var string
* @access private
*/
var $file;
/**
* File Cache (read-write a file);
* @var array object
* @access private
*/
var $cache;
/**
* File cache size in bytes.
* @var integer
* @access private
*/
var $cacheSize;
/**
* Actual file position
* @var integer
* @access private
*/
var $position;
/**
* Is End Of file?
* @access private
* @var bool
*/
var $eof;
var $stat;
function FEP_client() {
$this->position=0;
$this->cacheSize= 16384; /* minimun operation size */
$this->http = new http_class();
$http = & $this->http;
$http->timeout=0;
$http->data_timeout=0;
$http->debug=0;
$http->html_debug=0;
$http->user_agent="Cesar D. Rodas' FEP Class (+http://cesars.phpclasses.org/fep)";
$http->follow_redirect=1;
$http->redirection_limit=5;
$http->exclude_address="";
$http->protocol_version="1.1";
$this->http = &$http;
$this->contructed = true;
$this->position=0;
$this->buffer="";
}
/**
* Open a remote file.
*
* $f = $fep->Open("server.com","/file.php","r");
*
* For bandwidth save, the permission aren't checked on
* the Open, only when the operation (read,write) are doing
* permission are checked.
*
* @param string $server FEP Server to connect (URL)
* @param string $path Path to open
* @param string $omode Open Mode
*
* @return bool True if success false on otherwise
*/
function Open($server, $path,$omode) {
$mode = & $this->mode;
$mode = 0;
switch( strtolower($omode) ) {
/* Read */
case "r":
case "rb":
$mode = read;
break;
/* Write */
case "w";
case "wb":
$mode = write;
break;
/* Append */
case "r+":
case "r+b":
case "a":
case "ab":
$mode = append;
break;
default:
return false;
}
$this->server = $server;
$this->SetPath($path);
return $this->Stat();
}
/**
* File Stat.
*
* Get information about a given file
* @access public
* @param string $path File name, by default an open file.
* @return boolean true is success or false on otherwise
*/
function Stat($path='') {
if ($path != '')
$this->SetPath($path);
$this->HttpQuery('GET','INFO','',$headers,$response);
if ( $headers['fep-response'] != 'OK') {
trigger_error($response,E_USER_WARNING);
return false;
}
$e = & $this->stat;
list($e['mode'],$e['size'],$e['atime'],$e['mtime'],$e['ctime']) = explode("|",$response);
return true;
}
/**
* Write into the open file
*
* Writes into a cache-section and mark it as dirty for sincronize with the server
* as soon as posible.
* @param string $txt Texto to save
* @param integer $size Size of the text
* @access public
* @return integer Size of the writen text
*/
function Write($txt, $size=-1) {
$cache = & $this->cache;
if ($size==-1) $size=strlen($txt);
$bucket=ceil($this->position/$this->cacheSize) - 1; /* bucket of cache were to search */
$bucket=$bucket < 0 ? 0 : $bucket;
$writen=0; /* bytes writed */
/* write in the cache and mark as dirty */
while ($writen < $size) {
if ( !isset($cache[$bucket]) ) {
/* create a new cache object */
$cache[$bucket] = new httpCacheObject();
$cache[$bucket]->value = '';
$cache[$bucket]->size = 0;
}
$bucketSize = $cache[$bucket]->size;
if ( $this->cacheSize == $bucketSize) {
$bucket++;
continue;
}
$tmp = substr($txt, $writen, $this->cacheSize - $bucketSize);
$len = strlen($tmp);
$cache[$bucket]->value .= $tmp; /* increment cache */
$cache[$bucket]->size = $bucketSize + $len; /* increment cache size */
$cache[$bucket]->dirty = true; /* mark as dirty, need to sync as soon as posible */
/**
* Also change the Filesize in the memory, that is for read from memory
* what is yet uncommited. Useful on "a" and "rb" file open.
*/
$this->filesize = (count($cache)-1) * $this->cacheSize + $cache[count($cache)-1]->size;
$writen += $len;
$bucket++;
}
$this->position += $writen;
return $size;
}
/**
* File read.
*
* First search in cache, is not found, calls CacheRequest and returns.
*
* @param integer $size Size to read.
* @return string Content of the file.
* @access public
*/
function Read($size) {
$cache = & $this->cache;
$cachesize = & $this->cacheSize;
$pos = & $this->position;
$bucket=ceil(($pos+1)/$cachesize) - 1; /* bucket of cache were to search */
$bucket=$bucket < 0 ? 0 : $bucket;
$read = 0; /* bytes writed */
$start = $pos % $cachesize;
$res = '';
while ($read < $size && !$this->stream_eof() ) {
if ( !isset($cache[$bucket]) ) {
/* create a new cache object */
$cache[$bucket] = new httpCacheObject();
$cache[$bucket]->size = 0;
$this->HttpQuery('GET','READ','',$headers,$response,$pos, $cachesize);
if ( $headers['fep-response'] != 'OK') {
/**
* At this moment, the project stop the execution
* by simulation and EOF when the first error is arraised.
* TODO: Make a better system for check the read-error
* and have a system to avoid faults by re-asking to the server
* or stop on FILE_NOT_FOUND or UNAUTH.
*/
trigger_error($response,E_USER_WARNING);
$this->eof = true;
return;
}
$cache[$bucket]->value = $response;
$cache[$bucket]->size = $headers['fep-length'];
}
$tmp = substr($cache[$bucket]->value, $start, ($size - $read) % $cachesize);
$start = 0;
$e = strlen($tmp);
$read += $e;
$this->position += $e;
$res .= $tmp;
$bucket++;
}
return $res;
}
/**
* Syncronize server
*
* Sendes dirty cache to the server and those cache
* became clean cache until the cache is changed.
*
* The only function that do the HTTP request for write
* is this method. No other can write directly
*/
function Syncronize() {
$cache = & $this->cache;
$http = & $this->http;
for($i=0; isSet($cache[$i]); $i++) {
if ($cache[$i]->dirty) {
$posStart = $i * $this->cacheSize;
$this->HttpQuery('POST','WRITE',$cache[$i]->value,$headers,$response,$posStart, $cache[$i]->size);
if ( $headers['fep-response'] != 'OK') {
trigger_error($response,E_USER_WARNING);
}
$cache[$i]->dirty = false; /* the page is syncronized! */
}
}
}
/**
* End object.
*
* Syncronize dirty pages, destroy conections, destroy
* cache.
*
*/
function Destroy() {
$http = & $this->http;
$this->Syncronize();
$http->close(); /* destroy http connection */
unset($this->cache); /* destroy cache*/
}
function GenAuthCode(&$arguments) {
$header = &$arguments['Headers'];
$header['FEP-DATE'] = gmdate("D, d M Y G:i:s T");
$header['FEP-USER'] = FEP_USER;
$header['FEP-VERSION'] = "1.0";
$header['FEP-KEY'] = $this->genAuthKey($header['FEP-USER'], FEP_PASS, $header['FEP-PATH'], $header['FEP-ACTION'], $header['FEP-DATE']);
}
function HttpQuery($method,$action, $text, &$gHeaders, &$fepResponse,$starPos=-1, $length=-1) {
$http = & $this->http;
$http->request_method= $method;
$http->GetRequestArguments($this->server,$arguments); /* parse arguments */
$header = & $arguments['Headers'];
$header['FEP-PATH'] = $this->file;
if ( $starPos != -1) {
$header['Content-Length'] = $length;
$header['FEP-START'] = $starPos;
$header['FEP-LENGTH'] = $length;
}
$header['FEP-ACTION'] = $action;
if ( strlen($text) > 0)
$arguments['Body'] = $text;
$this->GenAuthCode($arguments);
/* open */
$http->Open($arguments);
/* send request */
$http->SendRequest($arguments);
/* get response headers */
$http->ReadReplyHeaders($tmp);
$gHeaders = $tmp;
$fepResponse='';
$http->response_status.=""; //convert to string
if ($http->response_status[0] != 2) {
return false;
}
/*Something were wrong*/
for(;;)
{
$error=$http->ReadReplyBody($body,1000);
if($error!="" || strlen($body)==0) break;
$fepResponse.=($body);
}
$http->Close();
return true;
}
function SetPath($path) {
$this->file = substr($path,6);
}
}
/**
* Cliente Class, Stream Wrapper version
*
* @package File Exchange Protocol
* @version 1.0
* @author Cesar D. Rodas <hide@address.com>
* @license BSD License
*/
class FEP_client_PHP_STREAM_WRAPPER extends FEP_client {
var $actualDir;
var $dirList;
function FEP_client_PHP_STREAM_WRAPPER() {
$this->FEP_client();
$this->server = FEP_SERVER;
}
function stream_tell() {
return $this->position;
}
function stream_open($path, $mode, $options, &$opened_path) {
if ( !defined('FEP_SERVER') ) return false;
return $this->Open(FEP_SERVER,$path,$mode);
}
function stream_read ( $count ) {
return $this->Read($count);
}
function stream_stat() {
return $this->stat;
}
function rmdir($path) {
return $this->GeneralQuery($path, 'GET','RMDIR',$head,$resp);
}
function mkdir($path) {
return $this->GeneralQuery($path, 'GET','MKDIR',$head,$resp);
}
function unlink($path) {
return $this->GeneralQuery($path, 'GET','UNLINK',$head,$resp);
}
function url_stat ( $path ) {
$this->FEP_client_PHP_STREAM_WRAPPER();
return $this->stat($path) ? $this->stat : false ;
}
function dir_opendir($path ) {
$this->dir_rewinddir();
if ( $this->GeneralQuery($path, 'GET','LIST',$head,$resp) )
{
$this->dirList=explode("\r\n",$resp);
return true;
}
return false;
}
function dir_rewinddir() {
$this->actualDir=0;
}
function dir_closedir() {
$this->dir_rewinddir();
$this->dirList = array();
}
function dir_readdir() {
return (count($this->dirList) > $this->actualDir) ? $this->dirList[ $this->actualDir++ ] : false;
}
function stream_flush() {
return $this->Syncronize();
}
function stream_write($data) {
return $this->Write($data);
}
function stream_close() {
return $this->Destroy();
}
function stream_eof() {
return $this->eof || $this->stat['size'] <= $this->position;
}
function stream_seek($offset, $whence)
{
switch ($whence) {
case SEEK_SET:
if ($offset < $this->stat['size'] && $offset >= 0) {
$this->position = $offset;
return true;
} else {
return false;
}
break;
case SEEK_CUR:
if ( $this->position + $offset < $this->stat['size']) {
$this->position += $offset;
return true;
} else {
return false;
}
break;
case SEEK_END:
if ($this->stat['size'] + $offset < $this->stat['size']) {
$this->position = $this->stat['size'] + $offset;
return true;
} else {
return false;
}
break;
default:
return false;
}
}
function GeneralQuery($path, $type,$command, &$head, &$resp) {
$this->FEP_client_PHP_STREAM_WRAPPER();
$this->SetPath($path);
$this->HttpQuery($type,$command,'',$headers,$response);
$head = $headers;
$resp = $response;
$this->Destroy();
if ( $headers['fep-response'] != 'OK') {
trigger_error($response,E_USER_WARNING);
return false;
}
return true;
}
}
stream_wrapper_register("fep","FEP_client_PHP_STREAM_WRAPPER") or die("Failed to register protocol fep");
?>