From d896c19011d1cb74440e78bf6ff2aca4a999dd7c Mon Sep 17 00:00:00 2001 From: Scott Shambarger Date: Sat, 31 Mar 2012 20:16:42 -0700 Subject: [PATCH 01/12] cal import should group events by uid, export should have correct newlines, fix for oc-384 --- apps/calendar/export.php | 2 +- apps/calendar/import.php | 128 +++++++++++++++++++++------------------ 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/apps/calendar/export.php b/apps/calendar/export.php index 2736eec96c..db3a83c92e 100644 --- a/apps/calendar/export.php +++ b/apps/calendar/export.php @@ -11,7 +11,7 @@ OC_Util::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); $cal = isset($_GET['calid']) ? $_GET['calid'] : NULL; $event = isset($_GET['eventid']) ? $_GET['eventid'] : NULL; -$nl = "\n\r"; +$nl = "\r\n"; if(isset($cal)){ $calendar = OC_Calendar_App::getCalendar($cal); $calobjects = OC_Calendar_Object::all($cal); diff --git a/apps/calendar/import.php b/apps/calendar/import.php index b1c6f91df8..e5a5f0837f 100644 --- a/apps/calendar/import.php +++ b/apps/calendar/import.php @@ -10,7 +10,8 @@ ob_start(); require_once ('../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$nl = "\n"; +$nl="\r\n"; +$comps = array('VEVENT'=>true, 'VTODO'=>true, 'VJOURNAL'=>true); $progressfile = 'import_tmp/' . md5(session_id()) . '.txt'; if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); @@ -29,85 +30,94 @@ if($_POST['method'] == 'new'){ } $id = $_POST['id']; } -//analyse the calendar file if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); fwrite($progressfopen, '20'); fclose($progressfopen); } -$searchfor = array('VEVENT', 'VTODO', 'VJOURNAL'); -$parts = $searchfor; -$filearr = explode($nl, $file); -$inelement = false; -$parts = array(); +// normalize the newlines +$file = str_replace(array("\r","\n\n"), array("\n","\n"), $file); +$lines = explode("\n", $file); +unset($file); +if(is_writable('import_tmp/')){ + $progressfopen = fopen($progressfile, 'w'); + fwrite($progressfopen, '30'); + fclose($progressfopen); +} +// analyze the file, group components by uid, and keep refs to originating calendar object +// $cals is array calendar objects, keys are 1st line# $cal, ie array( $cal => $caldata ) +// $caldata is array( 'first' => 1st component line#, 'last' => last comp line#, 'end' => end line# ) +// $caldata is used to create prefix/suffix strings when building import text +// $uids is array of component arrays, keys are $uid, ie array( $uid => array( $beginlineno => $component ) ) +// $component is array( 'end' => end line#, 'cal'=> $cal ) +$comp=$uid=$cal=false; +$cals=$uids=array(); $i = 0; -foreach($filearr as $line){ - foreach($searchfor as $search){ - if(substr_count($line, $search) == 1){ - list($attr, $val) = explode(':', $line); - if($attr == 'BEGIN'){ - $parts[]['begin'] = $i; - $inelement = true; +foreach($lines as $line) { + + if(strpos($line, ':')!==false) { + list($attr, $val) = explode(':', strtoupper($line)); + if ($attr == 'BEGIN' && $val == 'VCALENDAR') { + $cal = $i; + $cals[$cal] = array('first'=>$i,'last'=>$i,'end'=>$i); + } elseif ($attr =='BEGIN' && $cal!==false && isset($comps[$val])) { + $comp = $val; + $beginNo = $i; + } elseif ($attr == 'END' && $cal!==false && $val == 'VCALENDAR') { + if($comp!==false) { + unset($cals[$cal]); // corrupt calendar, unset it + } else { + $cals[$cal]['end'] = $i; } - if($attr == 'END'){ - $parts[count($parts) - 1]['end'] = $i; - $inelement = false; + $comp=$uid=$cal=false; // reset calendar + } elseif ($attr == 'END' && $comp!==false && $val == $comp) { + if(! $uid) { + $uid = OC_Calendar_Object::createUID(); } + $uids[$uid][$beginNo] = array('end'=>$i, 'cal'=>$cal); + if ($cals[$cal]['first'] == $cal) { + $cals[$cal]['first'] = $beginNo; + } + $cals[$cal]['last'] = $i; + $comp=$uid=false; // reset component + } elseif ($attr =="UID" && $comp!==false) { + list($attr, $uid) = explode(':', $line); } } $i++; } -//import the calendar +// import the calendar if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '40'); + fwrite($progressfopen, '60'); fclose($progressfopen); } -$start = ''; -for ($i = 0; $i < $parts[0]['begin']; $i++) { - if($i == 0){ - $start = $filearr[0]; - }else{ - $start .= $nl . $filearr[$i]; - } -} -$end = ''; -for($i = $parts[count($parts) - 1]['end'] + 1;$i <= count($filearr) - 1; $i++){ - if($i == $parts[count($parts) - 1]['end'] + 1){ - $end = $filearr[$parts[count($parts) - 1]['end'] + 1]; - }else{ - $end .= $nl . $filearr[$i]; - } -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '50'); - fclose($progressfopen); -} -$importready = array(); -foreach($parts as $part){ - for($i = $part['begin']; $i <= $part['end'];$i++){ - if($i == $part['begin']){ - $content = $filearr[$i]; - }else{ - $content .= $nl . $filearr[$i]; +foreach($uids as $uid) { + + $prefix=$suffix=$content=array(); + foreach($uid as $begin=>$details) { + + $cal = $details['cal']; + if(!isset($cals[$cal])) { + continue; // from corrupt/incomplete calendar } + $cdata = $cals[$cal]; + // if we have multiple components from different calendar objects, + // we should really merge their elements (enhancement?) -- 1st one wins for now. + if(! count($prefix)) { + $prefix = array_slice($lines, $cal, $cdata['first'] - $cal); + } + if(! count($suffix)) { + $suffix = array_slice($lines, $cdata['last']+1, $cdata['end'] - $cdata['last']); + } + $content = array_merge($content, array_slice($lines, $begin, $details['end'] - $begin + 1)); } - $importready[] = $start . $nl . $content . $nl . $end; -} -if(is_writable('import_tmp/')){ - $progressfopen = fopen($progressfile, 'w'); - fwrite($progressfopen, '70'); - fclose($progressfopen); -} -if(count($parts) == 1){ - OC_Calendar_Object::add($id, $file); -}else{ - foreach($importready as $import){ + if(count($content)) { + $import = join($nl, array_merge($prefix, $content, $suffix)) . $nl; OC_Calendar_Object::add($id, $import); } } -//done the import +// finished import if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); fwrite($progressfopen, '100'); From 26e9a0dd139a94d6876df0be49a8d40b8976d65a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 17 Apr 2012 20:56:53 +0200 Subject: [PATCH 02/12] bugfixes for encryption library and test cases --- apps/files_encryption/lib/crypt.php | 55 +++++++++++----------- apps/files_encryption/tests/encryption.php | 36 ++++++++++++++ 2 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 apps/files_encryption/tests/encryption.php diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 246d4f672d..8cf9451c63 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -119,7 +119,7 @@ class OC_Crypt { */ public static function encrypt( $content, $key='') { $bf = self::getBlowfish($key); - return($bf->encrypt($content)); + return $bf->encrypt($content); } /** @@ -132,61 +132,62 @@ class OC_Crypt { */ public static function decrypt( $content, $key='') { $bf = self::getBlowfish($key); - return($bf->decrypt($content)); + $data=$bf->decrypt($content); + return rtrim($data, "\0"); } /** * @brief encryption of a file - * @param $filename - * @param $key the encryption key + * @param string $source + * @param string $target + * @param string $key the decryption key * * This function encrypts a file */ - public static function encryptfile( $filename, $key) { - $handleread = fopen($filename, "rb"); - if($handleread<>FALSE) { - $handlewrite = fopen($filename.OC_Crypt::$encription_extension, "wb"); + public static function encryptFile( $source, $target, $key='') { + $handleread = fopen($source, "rb"); + if($handleread!=FALSE) { + $handlewrite = fopen($target, "wb"); while (!feof($handleread)) { $content = fread($handleread, 8192); $enccontent=OC_CRYPT::encrypt( $content, $key); fwrite($handlewrite, $enccontent); } fclose($handlewrite); - unlink($filename); + fclose($handleread); } - fclose($handleread); } - /** - * @brief decryption of a file - * @param $filename - * @param $key the decryption key - * - * This function decrypts a file - */ - public static function decryptfile( $filename, $key) { - $handleread = fopen($filename.OC_Crypt::$encription_extension, "rb"); - if($handleread<>FALSE) { - $handlewrite = fopen($filename, "wb"); + /** + * @brief decryption of a file + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function decryptFile( $source, $target, $key='') { + $handleread = fopen($source, "rb"); + if($handleread!=FALSE) { + $handlewrite = fopen($target, "wb"); while (!feof($handleread)) { $content = fread($handleread, 8192); $enccontent=OC_CRYPT::decrypt( $content, $key); fwrite($handlewrite, $enccontent); } fclose($handlewrite); - unlink($filename.OC_Crypt::$encription_extension); + fclose($handleread); } - fclose($handleread); } /** * encrypt data in 8192b sized blocks */ - public static function blockEncrypt($data){ + public static function blockEncrypt($data, $key=''){ $result=''; while(strlen($data)){ - $result=self::encrypt(substr($data,0,8192)); + $result.=self::encrypt(substr($data,0,8192),$key); $data=substr($data,8192); } return $result; @@ -195,10 +196,10 @@ class OC_Crypt { /** * decrypt data in 8192b sized blocks */ - public static function blockDecrypt($data){ + public static function blockDecrypt($data, $key=''){ $result=''; while(strlen($data)){ - $result=self::decrypt(substr($data,0,8192)); + $result.=self::decrypt(substr($data,0,8192),$key); $data=substr($data,8192); } return $result; diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php new file mode 100644 index 0000000000..140fe6b126 --- /dev/null +++ b/apps/files_encryption/tests/encryption.php @@ -0,0 +1,36 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_Encryption extends UnitTestCase { + function testEncryption(){ + $key=uniqid(); + $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; + $source=file_get_contents($file); //nice large text file + $encrypted=OC_Crypt::encrypt($source,$key); + $decrypted=OC_Crypt::decrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $encrypted=OC_Crypt::blockEncrypt($source,$key); + $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $tmpFileEncrypted=OC_Helper::tmpFile(); + OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); + $encrypted=file_get_contents($tmpFileEncrypted); + $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); + $this->assertNotEqual($encrypted,$source); + $this->assertEqual($decrypted,$source); + + $tmpFileDecrypted=OC_Helper::tmpFile(); + OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); + $decrypted=file_get_contents($tmpFileDecrypted); + $this->assertEqual($decrypted,$source); + } +} From b39c3d4c4edbb9a7fd2be22e2d2684e4185ca0e5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 17 Apr 2012 23:10:14 +0200 Subject: [PATCH 03/12] make use of the fact that stream_read will always read 8192 bytes for encryption stream https://bugs.php.net/bug.php?id=21641 --- apps/files_encryption/lib/cryptstream.php | 38 ++++++--------- apps/files_encryption/tests/stream.php | 56 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 apps/files_encryption/tests/stream.php diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index 86583096f1..21fa38e4b5 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -64,29 +64,19 @@ class OC_CryptStream{ } public function stream_read($count){ - $pos=0; - $currentPos=ftell($this->source); - $offset=$currentPos%8192; - $result=''; - if($offset>0){ - if($this->meta['seekable']){ - fseek($this->source,-$offset,SEEK_CUR);//if seeking isnt supported the internal read buffer will be used - }else{ - $pos=strlen($this->readBuffer); - $result=$this->readBuffer; - } + //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 + //This makes this function a lot simpler but will breake everything the moment it's fixed + if($count!=8192){ + OC_Log::write('files_encryption','php bug 21641 no longer holds, decryption will not work',OC_Log::FATAL); + die(); } - while($count>$pos){ - $data=fread($this->source,8192); - $pos+=8192; - if(strlen($data)){ - $result.=OC_Crypt::decrypt($data); - } + $data=fread($this->source,8192); + if(strlen($data)){ + $result=OC_Crypt::decrypt($data); + }else{ + $result=''; } - if(!$this->meta['seekable']){ - $this->readBuffer=substr($result,$count); - } - return substr($result,0,$count); + return $result; } public function stream_write($data){ @@ -107,8 +97,10 @@ class OC_CryptStream{ $oldPos=ftell($this->source); $encryptedBlock=fread($this->source,8192); fseek($this->source,$oldPos); - $block=OC_Crypt::decrypt($encryptedBlock); - $data.=substr($block,strlen($data)); + if($encryptedBlock){ + $block=OC_Crypt::decrypt($encryptedBlock); + $data.=substr($block,strlen($data)); + } } $encrypted=OC_Crypt::encrypt(substr($data,0,8192)); fwrite($this->source,$encrypted); diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php new file mode 100644 index 0000000000..578b091a36 --- /dev/null +++ b/apps/files_encryption/tests/stream.php @@ -0,0 +1,56 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_CryptStream extends UnitTestCase { + private $tmpFiles=array(); + + function testStream(){ + $stream=$this->getStream('test1','w'); + fwrite($stream,'foobar'); + fclose($stream); + + $stream=$this->getStream('test1','r'); + $data=fread($stream,6); + fclose($stream); + $this->assertEqual('foobar',$data); + + $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; + $source=fopen($file,'r'); + $target=$this->getStream('test2','w'); + OC_Helper::streamCopy($source,$target); + fclose($target); + fclose($source); + + $stream=$this->getStream('test2','r'); + $data=stream_get_contents($stream); + $original=file_get_contents($file); + $this->assertEqual(strlen($original),strlen($data)); + $this->assertEqual($original,$data); + } + + /** + * get a cryptstream to a temporary file + * @param string $id + * @param string $mode + * @return resource + */ + function getStream($id,$mode){ + if($id===''){ + $id=uniqid(); + } + if(!isset($this->tmpFiles[$id])){ + $file=OC_Helper::tmpFile(); + $this->tmpFiles[$id]=$file; + }else{ + $file=$this->tmpFiles[$id]; + } + $stream=fopen($file,$mode); + OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy','stream'=>$stream); + return fopen('crypt://streams/'.$id,$mode); + } +} From d1ad4dc8d60a656606b6c237df9f4c931f828c73 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 18 Apr 2012 16:02:35 +0200 Subject: [PATCH 04/12] add test cases for cryptstream --- apps/files_encryption/lib/proxy.php | 7 +++++++ apps/files_encryption/tests/encryption.php | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index c68df06f9f..a0de411a7b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -28,6 +28,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ private static $blackList=null; //mimetypes blacklisted from encryption private static $metaData=array(); //metadata cache + private static $enableEncryption=null; /** * check if a file should be encrypted during write @@ -35,6 +36,12 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ * @return bool */ private static function shouldEncrypt($path){ + if(is_null($this->enableEncryption)){ + $this->enableEncryption=(OC_Appconfig::getValue('files_encryption','enabled','true')=='true'); + } + if(!$this->enableEncryption){ + return false; + } if(is_null(self::$blackList)){ self::$blackList=explode(',',OC_Appconfig::getValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); } diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 140fe6b126..1309325671 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -15,6 +15,12 @@ class Test_Encryption extends UnitTestCase { $decrypted=OC_Crypt::decrypt($encrypted,$key); $this->assertNotEqual($encrypted,$source); $this->assertEqual($decrypted,$source); + + $chunk=substr($source,0,8192); + $encrypted=OC_Crypt::encrypt($chunk,$key); + $this->assertEqual(strlen($chunk),strlen($encrypted)); + $decrypted=OC_Crypt::decrypt($encrypted,$key); + $this->assertEqual($decrypted,$chunk); $encrypted=OC_Crypt::blockEncrypt($source,$key); $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); From bdb3962b8dc42493a9db87882d1671d6d3a53eea Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 18 Apr 2012 20:24:22 +0200 Subject: [PATCH 05/12] add function to get mimetype from string --- lib/helper.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/helper.php b/lib/helper.php index eed9491776..beb10280d9 100755 --- a/lib/helper.php +++ b/lib/helper.php @@ -353,6 +353,26 @@ class OC_Helper { return $mimeType; } + /** + * get the mimetype form a data string + * @param string data + * @return string + */ + static function getStringMimeType($data){ + if(function_exists('finfo_open') and function_exists('finfo_file')){ + $finfo=finfo_open(FILEINFO_MIME); + return finfo_buffer($finfo, $data); + }else{ + $tmpFile=OC_Helper::tmpFile(); + $fh=fopen($tmpFile,'wb'); + fwrite($fh,$data,8024); + fclose($fh); + $mime=self::getMimeType($tmpFile); + unset($tmpFile); + return $mime; + } + } + /** * @brief Checks $_REQUEST contains a var for the $s key. If so, returns the html-escaped value of this var; otherwise returns the default value provided by $d. * @param $s name of the var to escape, if set. From aceda02887eb4a52188f0308c6404cc9f201ce79 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 18 Apr 2012 20:46:00 +0200 Subject: [PATCH 06/12] check if removed folders are really gone --- tests/lib/filestorage.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/lib/filestorage.php b/tests/lib/filestorage.php index 4858234a2d..b58e28cefd 100644 --- a/tests/lib/filestorage.php +++ b/tests/lib/filestorage.php @@ -68,6 +68,15 @@ abstract class Test_FileStorage extends UnitTestCase { $this->assertFalse($this->instance->file_exists('/folder')); $this->assertFalse($this->instance->rmdir('/folder'));//cant remove non existing folders + + $dh=$this->instance->opendir('/'); + $content=array(); + while($file=readdir($dh)){ + if($file!='.' and $file!='..'){ + $content[]=$file; + } + } + $this->assertEqual(array(),$content); } /** From 9a707e10bf70619bf1a553b2bc56c989f9725ab2 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 18 Apr 2012 20:54:07 +0200 Subject: [PATCH 07/12] add SWIFT (openstack object storage) storage backend --- 3rdparty/php-cloudfiles/.gitignore | 3 + 3rdparty/php-cloudfiles/AUTHORS | 11 + 3rdparty/php-cloudfiles/COPYING | 27 + 3rdparty/php-cloudfiles/Changelog | 93 + 3rdparty/php-cloudfiles/README | 73 + 3rdparty/php-cloudfiles/cloudfiles.php | 2599 +++++++++++++++++ .../php-cloudfiles/cloudfiles_exceptions.php | 41 + 3rdparty/php-cloudfiles/cloudfiles_http.php | 1488 ++++++++++ apps/files_external/appinfo/app.php | 1 + apps/files_external/lib/swift.php | 488 ++++ apps/files_external/tests/config.php | 9 +- apps/files_external/tests/swift.php | 32 + 12 files changed, 4864 insertions(+), 1 deletion(-) create mode 100644 3rdparty/php-cloudfiles/.gitignore create mode 100644 3rdparty/php-cloudfiles/AUTHORS create mode 100644 3rdparty/php-cloudfiles/COPYING create mode 100644 3rdparty/php-cloudfiles/Changelog create mode 100644 3rdparty/php-cloudfiles/README create mode 100644 3rdparty/php-cloudfiles/cloudfiles.php create mode 100644 3rdparty/php-cloudfiles/cloudfiles_exceptions.php create mode 100644 3rdparty/php-cloudfiles/cloudfiles_http.php create mode 100644 apps/files_external/lib/swift.php create mode 100644 apps/files_external/tests/swift.php diff --git a/3rdparty/php-cloudfiles/.gitignore b/3rdparty/php-cloudfiles/.gitignore new file mode 100644 index 0000000000..875b72b27e --- /dev/null +++ b/3rdparty/php-cloudfiles/.gitignore @@ -0,0 +1,3 @@ +*.swp +*~ +tests/output.log diff --git a/3rdparty/php-cloudfiles/AUTHORS b/3rdparty/php-cloudfiles/AUTHORS new file mode 100644 index 0000000000..a92cfa7c1a --- /dev/null +++ b/3rdparty/php-cloudfiles/AUTHORS @@ -0,0 +1,11 @@ +Current maintainer: + Conrad Weidenkeller + +Previous maintainer: + Eric "EJ" Johnson + Chmouel Boudjnah + +Contributors: + Paul Kehrer + Ben Arwin + Jordan Callicoat diff --git a/3rdparty/php-cloudfiles/COPYING b/3rdparty/php-cloudfiles/COPYING new file mode 100644 index 0000000000..0e10239d00 --- /dev/null +++ b/3rdparty/php-cloudfiles/COPYING @@ -0,0 +1,27 @@ +Unless otherwise noted, all files are released under the MIT license, +exceptions contain licensing information in them. + + Copyright (C) 2008 Rackspace US, Inc. + +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 OR COPYRIGHT HOLDERS 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. + +Except as contained in this notice, the name of Rackspace US, Inc. shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from Rackspace US, Inc. + diff --git a/3rdparty/php-cloudfiles/Changelog b/3rdparty/php-cloudfiles/Changelog new file mode 100644 index 0000000000..df9303c3e5 --- /dev/null +++ b/3rdparty/php-cloudfiles/Changelog @@ -0,0 +1,93 @@ +1.7.10 Conrad Weidenkeller + * Added Streaming URI Functionality + +1.7.9 Conrad Weidenkeller + * Added Manifest file support for Large Objects + +1.7.8 Conrad Weidenkeller + * Added CDN SSL URI Stuff + +1.7.7 Conrad Weidenkeller + * Added CDN Purge Functionality + +1.7.6 - Chmouel Boudjnah + * Add Cloud UK Support (conrad.weidenkeller). + +1.7.5 - Conrad Weidenkeller + * Added the ability to list only currently enabled CDN containers + * Added curl timeout to CF_Http + * Fixed some logic errors in some if statements. + +1.7.4 - Conrad Weidenkeller + * Added Manual SSL support for MacOSX users + +1.7.3 - Conrad Weidenkeller + * Fixed a Small Bug where some users were seeing response bodies for PUTs + +1.7.1 - Conrad Weidenkeller + * Added Support for Auth Token Caching. + +1.7.0 - Chmouel Boudjnah + * Adjust api auth to rackspacecloud not mosso (mshuler). + +1.6.2 - Chmouel Boudjnah + * Add a close method to close all the current connection. + * Fix when container_name is named 0. + +1.6.1 - Chmouel Boudjnah + * Fix setting etag on objects. + * Fix throwing proper exception when an invalid etag has been set. + * Fix throwing proper exception when no content type has been set. + +1.6.0 - Chmouel Boudjnah + * Add CDN ACL restriction by referrer feature. + * Add CDN ACL restriction by User Agent feature. + * Add documentation for log_retention method. + * Return True if log_retention as succeeded. + * Invalid the PHP stats cache before getting filesize. + +1.5.1 - Chmouel Boudjnah - 20091020 + * If the environement variable RACKSPACE_SERVICENET is defined then force to + connect via rakcspace servicenet. + +1.5.0 - Chmouel Boudjnah - 20091015 + * Add the option servicenet to connection to use Rackspace service net + instead of public network. + +1.4.0 - Chmouel Boudjnah - 20090808 + + * Add the ability to store the container log. + +1.3.2 - Chmouel Boudjnah - 20090606 + + * Change the Unit Tests to phpunit. + * Automatically set the updated CA bundle when on the windows OS. + * More simplification of the mimetype detection and support for PHP 5.3. + * Fix documentation information about the ttl for cached object. + * Use the hash library to compute MD5 for streams instead of storing the + stream in memory. + * Fix CF_Connection::get_containers to display the container name properly. + +1.3.1 - - 20090325 + + * Simplify use of FileInfo, remove packaged MIME/Magic file + * Throw Exception if no Content-Type is set + * Fix bug with tracking bytes transferred + * Support/tested on Windows XP (PHP v5.2.9) + +1.3.0 - - 20090311 + + * Support for list operations in JSON/XML + * Added support for FileInfo automatic Content-Type/MIME detection + * Workaround for cURL's old CA bundle with CF_Connection->ssl_use_cabundle() + * Supports limit/marker on Account and Container lists + * Support "pathname" traversal on Container lists + * Helper function on Container to create directory marker Objects + * Support for chunked transfer on PUT requests + +1.2.3 - - 20081210 + + * Improved in-line comments and generated HTML docs + * Callbacks for read/write progress on CF_Connection class + * Fixed minor bugs + * Started this Changelog diff --git a/3rdparty/php-cloudfiles/README b/3rdparty/php-cloudfiles/README new file mode 100644 index 0000000000..4bcbeade1a --- /dev/null +++ b/3rdparty/php-cloudfiles/README @@ -0,0 +1,73 @@ +;; PHP Cloud Files API +;; ======================================================================== +;; This package contains the PHP API for the Cloud Files storage system. +;; +;; Please see http://www.rackspacecloud.com/ for more information regarding the +;; Cloud Files storage system. +;; +;; Install +;; ------------------------------------------------------------------------ +;; Extract this archive and make sure the source code files are in your +;; PHP "include path". To use the API in your source code, just make +;; sure to include/require the "cloudfiles.php" script. +;; +;; Requirements +;; ------------------------------------------------------------------------ +;; [mandatory] PHP version 5.x (developed against 5.2.0) +;; [mandatory] PHP's cURL module +;; [mandatory] PHP enabled with mbstring (multi-byte string) support +;; [suggested] PEAR FileInfo module (for Content-Type detection) +;; +;; Examples +;; ------------------------------------------------------------------------ +;; For sample code, please see the tests and API docs. +;; +;; Docs +;; ------------------------------------------------------------------------ +;; The included documentation was generated directly from the source +;; code files using the PHPDocumentor tool. +;; +;; This README file is actually the PHPDocumentor INI configuration file. +;; The following packages were installed via PEAR to generate the HTML +;; API documentation. +;; +;; * PEAR 1.4.11 (stable) +;; * PhpDocumentor 1.4.2 (stable) +;; * XML_Beautifier 1.2.0 (stable) +;; * XML_Parser 1.3.1 (stable) +;; * XML_Util 1.2.0 (stable) +;; +;; To re-generate the API docs, make sure the above software is +;; available and run: +;; rm -rf docs && phpdoc -c phpdoc.ini +;; +;; Tests +;; ------------------------------------------------------------------------ +;; The tests are based on phpunit and are run with PHPUnit 3.3.17 +;; please follow the instructions on : +;; +;; http://www.phpunit.de/manual/current/en/installation.html +;; +;; to install PHPUnit. When installed just run the command phpunit on +;; the top of the directory and it will launch the tests. +;; +;; The tests/Comprehensive.php is not enabled by default since +;; generating big files. If you want to run it you need to go in the +;; tests directory and run with phpunit Comprehensive.php +;; +;; ======================================================================== +;; The lines below here are the configuration settings for re-generating +;; the PHP API documentation. +;; +[Parse Data] +title = php-cloudfiles +hidden = false +parseprivate = off +javadocdesc = off +defaultpackagename = php-cloudfiles +defaultcategoryname = php-cloudfiles +target = docs +directory = . +ignore = share/,examples/,tests/,.git/,.gitignore,*.ini,*.swp +output=HTML:Smarty:PHP +readmeinstallchangelog = README,COPYING,AUTHORS,Changelog diff --git a/3rdparty/php-cloudfiles/cloudfiles.php b/3rdparty/php-cloudfiles/cloudfiles.php new file mode 100644 index 0000000000..5f7e2100a9 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles.php @@ -0,0 +1,2599 @@ + + * # Authenticate to Cloud Files. The default is to automatically try + * # to re-authenticate if an authentication token expires. + * # + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * $auth = new CF_Authentication($username, $api_key); + * # $auth->ssl_use_cabundle(); # bypass cURL's old CA bundle + * $auth->authenticate(); + * + * # Establish a connection to the storage system + * # + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Connection instance's 'ssl_use_cabundle()' method. + * # + * $conn = new CF_Connection($auth); + * # $conn->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * # Create a remote Container and storage Object + * # + * $images = $conn->create_container("photos"); + * $bday = $images->create_object("first_birthday.jpg"); + * + * # Upload content from a local file by streaming it. Note that we use + * # a "float" for the file size to overcome PHP's 32-bit integer limit for + * # very large files. + * # + * $fname = "/home/user/photos/birthdays/birthday1.jpg"; # filename to upload + * $size = (float) sprintf("%u", filesize($fname)); + * $fp = open($fname, "r"); + * $bday->write($fp, $size); + * + * # Or... use a convenience function instead + * # + * $bday->load_from_filename("/home/user/photos/birthdays/birthday1.jpg"); + * + * # Now, publish the "photos" container to serve the images by CDN. + * # Use the "$uri" value to put in your web pages or send the link in an + * # email message, etc. + * # + * $uri = $images->make_public(); + * + * # Or... print out the Object's public URI + * # + * print $bday->public_uri(); + * + * + * See the included tests directory for additional sample code. + * + * Requres PHP 5.x (for Exceptions and OO syntax) and PHP's cURL module. + * + * It uses the supporting "cloudfiles_http.php" module for HTTP(s) support and + * allows for connection re-use and streaming of content into/out of Cloud Files + * via PHP's cURL module. + * + * See COPYING for license information. + * + * @author Eric "EJ" Johnson + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles + */ + +/** + */ +require_once("cloudfiles_exceptions.php"); +require("cloudfiles_http.php"); +define("DEFAULT_CF_API_VERSION", 1); +define("MAX_CONTAINER_NAME_LEN", 256); +define("MAX_OBJECT_NAME_LEN", 1024); +define("MAX_OBJECT_SIZE", 5*1024*1024*1024+1); +define("US_AUTHURL", "https://auth.api.rackspacecloud.com"); +define("UK_AUTHURL", "https://lon.auth.api.rackspacecloud.com"); +/** + * Class for handling Cloud Files Authentication, call it's {@link authenticate()} + * method to obtain authorized service urls and an authentication token. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # NOTE: For UK Customers please specify your AuthURL Manually + * # There is a Predfined constant to use EX: + * # + * # $auth = new CF_Authentication("username, "api_key", NULL, UK_AUTHURL); + * # Using the UK_AUTHURL keyword will force the api to use the UK AuthUrl. + * # rather then the US one. The NULL Is passed for legacy purposes and must + * # be passed to function correctly. + * + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * # $auth->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * + * @package php-cloudfiles + */ +class CF_Authentication +{ + public $dbug; + public $username; + public $api_key; + public $auth_host; + public $account; + + /** + * Instance variables that are set after successful authentication + */ + public $storage_url; + public $cdnm_url; + public $auth_token; + + /** + * Class constructor (PHP 5 syntax) + * + * @param string $username Mosso username + * @param string $api_key Mosso API Access Key + * @param string $account Account name + * @param string $auth_host Authentication service URI + */ + function __construct($username=NULL, $api_key=NULL, $account=NULL, $auth_host=US_AUTHURL) + { + + $this->dbug = False; + $this->username = $username; + $this->api_key = $api_key; + $this->account_name = $account; + $this->auth_host = $auth_host; + + $this->storage_url = NULL; + $this->cdnm_url = NULL; + $this->auth_token = NULL; + + $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION); + } + + /** + * Use the Certificate Authority bundle included with this API + * + * Most versions of PHP with cURL support include an outdated Certificate + * Authority (CA) bundle (the file that lists all valid certificate + * signing authorities). The SSL certificates used by the Cloud Files + * storage system are perfectly valid but have been created/signed by + * a CA not listed in these outdated cURL distributions. + * + * As a work-around, we've included an updated CA bundle obtained + * directly from cURL's web site (http://curl.haxx.se). You can direct + * the API to use this CA bundle by calling this method prior to making + * any remote calls. The best place to use this method is right after + * the CF_Authentication instance has been instantiated. + * + * You can specify your own CA bundle by passing in the full pathname + * to the bundle. You can use the included CA bundle by leaving the + * argument blank. + * + * @param string $path Specify path to CA bundle (default to included) + */ + function ssl_use_cabundle($path=NULL) + { + $this->cfs_http->ssl_use_cabundle($path); + } + + /** + * Attempt to validate Username/API Access Key + * + * Attempts to validate credentials with the authentication service. It + * either returns True or throws an Exception. Accepts a single + * (optional) argument for the storage system API version. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * + * @param string $version API version for Auth service (optional) + * @return boolean True if successfully authenticated + * @throws AuthenticationException invalid credentials + * @throws InvalidResponseException invalid response + */ + function authenticate($version=DEFAULT_CF_API_VERSION) + { + list($status,$reason,$surl,$curl,$atoken) = + $this->cfs_http->authenticate($this->username, $this->api_key, + $this->account_name, $this->auth_host); + + if ($status == 401) { + throw new AuthenticationException("Invalid username or access key."); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Unexpected response (".$status."): ".$reason); + } + + if (!($surl || $curl) || !$atoken) { + throw new InvalidResponseException( + "Expected headers missing from auth service."); + } + $this->storage_url = $surl; + $this->cdnm_url = $curl; + $this->auth_token = $atoken; + return True; + } + /** + * Use Cached Token and Storage URL's rather then grabbing from the Auth System + * + * Example: + * + * #Create an Auth instance + * $auth = new CF_Authentication(); + * #Pass Cached URL's and Token as Args + * $auth->load_cached_credentials("auth_token", "storage_url", "cdn_management_url"); + * + * + * @param string $auth_token A Cloud Files Auth Token (Required) + * @param string $storage_url The Cloud Files Storage URL (Required) + * @param string $cdnm_url CDN Management URL (Required) + * @return boolean True if successful + * @throws SyntaxException If any of the Required Arguments are missing + */ + function load_cached_credentials($auth_token, $storage_url, $cdnm_url) + { + if(!$storage_url || !$cdnm_url) + { + throw new SyntaxException("Missing Required Interface URL's!"); + return False; + } + if(!$auth_token) + { + throw new SyntaxException("Missing Auth Token!"); + return False; + } + + $this->storage_url = $storage_url; + $this->cdnm_url = $cdnm_url; + $this->auth_token = $auth_token; + return True; + } + /** + * Grab Cloud Files info to be Cached for later use with the load_cached_credentials method. + * + * Example: + * + * #Create an Auth instance + * $auth = new CF_Authentication("UserName","API_Key"); + * $auth->authenticate(); + * $array = $auth->export_credentials(); + * + * + * @return array of url's and an auth token. + */ + function export_credentials() + { + $arr = array(); + $arr['storage_url'] = $this->storage_url; + $arr['cdnm_url'] = $this->cdnm_url; + $arr['auth_token'] = $this->auth_token; + + return $arr; + } + + + /** + * Make sure the CF_Authentication instance has authenticated. + * + * Ensures that the instance variables necessary to communicate with + * Cloud Files have been set from a previous authenticate() call. + * + * @return boolean True if successfully authenticated + */ + function authenticated() + { + if (!($this->storage_url || $this->cdnm_url) || !$this->auth_token) { + return False; + } + return True; + } + + /** + * Toggle debugging - set cURL verbose flag + */ + function setDebug($bool) + { + $this->dbug = $bool; + $this->cfs_http->setDebug($bool); + } +} + +/** + * Class for establishing connections to the Cloud Files storage system. + * Connection instances are used to communicate with the storage system at + * the account level; listing and deleting Containers and returning Container + * instances. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * # Create a connection to the storage/cdn system(s) and pass in the + * # validated CF_Authentication instance. + * # + * $conn = new CF_Connection($auth); + * + * # NOTE: Some versions of cURL include an outdated certificate authority (CA) + * # file. This API ships with a newer version obtained directly from + * # cURL's web site (http://curl.haxx.se). To use the newer CA bundle, + * # call the CF_Authentication instance's 'ssl_use_cabundle()' method. + * # + * # $conn->ssl_use_cabundle(); # bypass cURL's old CA bundle + * + * + * @package php-cloudfiles + */ +class CF_Connection +{ + public $dbug; + public $cfs_http; + public $cfs_auth; + + /** + * Pass in a previously authenticated CF_Authentication instance. + * + * Example: + * + * # Create the authentication instance + * # + * $auth = new CF_Authentication("username", "api_key"); + * + * # Perform authentication request + * # + * $auth->authenticate(); + * + * # Create a connection to the storage/cdn system(s) and pass in the + * # validated CF_Authentication instance. + * # + * $conn = new CF_Connection($auth); + * + * # If you are connecting via Rackspace servers and have access + * # to the servicenet network you can set the $servicenet to True + * # like this. + * + * $conn = new CF_Connection($auth, $servicenet=True); + * + * + * + * If the environement variable RACKSPACE_SERVICENET is defined it will + * force to connect via the servicenet. + * + * @param obj $cfs_auth previously authenticated CF_Authentication instance + * @param boolean $servicenet enable/disable access via Rackspace servicenet. + * @throws AuthenticationException not authenticated + */ + function __construct($cfs_auth, $servicenet=False) + { + if (isset($_ENV['RACKSPACE_SERVICENET'])) + $servicenet=True; + $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION); + $this->cfs_auth = $cfs_auth; + if (!$this->cfs_auth->authenticated()) { + $e = "Need to pass in a previously authenticated "; + $e .= "CF_Authentication instance."; + throw new AuthenticationException($e); + } + $this->cfs_http->setCFAuth($this->cfs_auth, $servicenet=$servicenet); + $this->dbug = False; + } + + /** + * Toggle debugging of instance and back-end HTTP module + * + * @param boolean $bool enable/disable cURL debugging + */ + function setDebug($bool) + { + $this->dbug = (boolean) $bool; + $this->cfs_http->setDebug($this->dbug); + } + + /** + * Close a connection + * + * Example: + * + * + * $conn->close(); + * + * + * + * Will close all current cUrl active connections. + * + */ + public function close() + { + $this->cfs_http->close(); + } + + /** + * Cloud Files account information + * + * Return an array of two floats (since PHP only supports 32-bit integers); + * number of containers on the account and total bytes used for the account. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * list($quantity, $bytes) = $conn->get_info(); + * print "Number of containers: " . $quantity . "\n"; + * print "Bytes stored in container: " . $bytes . "\n"; + * + * + * @return array (number of containers, total bytes stored) + * @throws InvalidResponseException unexpected response + */ + function get_info() + { + list($status, $reason, $container_count, $total_bytes) = + $this->cfs_http->head_account(); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_info(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return array($container_count, $total_bytes); + } + + /** + * Create a Container + * + * Given a Container name, return a Container instance, creating a new + * remote Container if it does not exit. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->create_container("my photos"); + * + * + * @param string $container_name container name + * @return CF_Container + * @throws SyntaxException invalid name + * @throws InvalidResponseException unexpected response + */ + function create_container($container_name=NULL) + { + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + if (!isset($container_name) or $container_name == "") + throw new SyntaxException("Container name not set."); + + if (strpos($container_name, "/") !== False) { + $r = "Container name '".$container_name; + $r .= "' cannot contain a '/' character."; + throw new SyntaxException($r); + } + if (strlen($container_name) > MAX_CONTAINER_NAME_LEN) { + throw new SyntaxException(sprintf( + "Container name exeeds %d bytes.", + MAX_CONTAINER_NAME_LEN)); + } + + $return_code = $this->cfs_http->create_container($container_name); + if (!$return_code) { + throw new InvalidResponseException("Invalid response (" + . $return_code. "): " . $this->cfs_http->get_error()); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->create_container($container_name); + #} + if ($return_code != 201 && $return_code != 202) { + throw new InvalidResponseException( + "Invalid response (".$return_code."): " + . $this->cfs_http->get_error()); + } + return new CF_Container($this->cfs_auth, $this->cfs_http, $container_name); + } + + /** + * Delete a Container + * + * Given either a Container instance or name, remove the remote Container. + * The Container must be empty prior to removing it. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $conn->delete_container("my photos"); + * + * + * @param string|obj $container container name or instance + * @return boolean True if successfully deleted + * @throws SyntaxException missing proper argument + * @throws InvalidResponseException invalid response + * @throws NonEmptyContainerException container not empty + * @throws NoSuchContainerException remote container does not exist + */ + function delete_container($container=NULL) + { + $container_name = NULL; + + if (is_object($container)) { + if (get_class($container) == "CF_Container") { + $container_name = $container->name; + } + } + if (is_string($container)) { + $container_name = $container; + } + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Must specify container object or name."); + + $return_code = $this->cfs_http->delete_container($container_name); + + if (!$return_code) { + throw new InvalidResponseException("Failed to obtain http response"); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->delete_container($container); + #} + if ($return_code == 409) { + throw new NonEmptyContainerException( + "Container must be empty prior to removing it."); + } + if ($return_code == 404) { + throw new NoSuchContainerException( + "Specified container did not exist to delete."); + } + if ($return_code != 204) { + throw new InvalidResponseException( + "Invalid response (".$return_code."): " + . $this->cfs_http->get_error()); + } + return True; + } + + /** + * Return a Container instance + * + * For the given name, return a Container instance if the remote Container + * exists, otherwise throw a Not Found exception. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * print "Number of Objects: " . $images->count . "\n"; + * print "Bytes stored in container: " . $images->bytes . "\n"; + * + * + * @param string $container_name name of the remote Container + * @return container CF_Container instance + * @throws NoSuchContainerException thrown if no remote Container + * @throws InvalidResponseException unexpected response + */ + function get_container($container_name=NULL) + { + list($status, $reason, $count, $bytes) = + $this->cfs_http->head_container($container_name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_container($container_name); + #} + if ($status == 404) { + throw new NoSuchContainerException("Container not found."); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response: ".$this->cfs_http->get_error()); + } + return new CF_Container($this->cfs_auth, $this->cfs_http, + $container_name, $count, $bytes); + } + + /** + * Return array of Container instances + * + * Return an array of CF_Container instances on the account. The instances + * will be fully populated with Container attributes (bytes stored and + * Object count) + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $clist = $conn->get_containers(); + * foreach ($clist as $cont) { + * print "Container name: " . $cont->name . "\n"; + * print "Number of Objects: " . $cont->count . "\n"; + * print "Bytes stored in container: " . $cont->bytes . "\n"; + * } + * + * + * @return array An array of CF_Container instances + * @throws InvalidResponseException unexpected response + */ + function get_containers($limit=0, $marker=NULL) + { + list($status, $reason, $container_info) = + $this->cfs_http->list_containers_info($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_containers(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response: ".$this->cfs_http->get_error()); + } + $containers = array(); + foreach ($container_info as $name => $info) { + $containers[] = new CF_Container($this->cfs_auth, $this->cfs_http, + $info['name'], $info["count"], $info["bytes"], False); + } + return $containers; + } + + /** + * Return list of remote Containers + * + * Return an array of strings containing the names of all remote Containers. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $container_list = $conn->list_containers(); + * print_r($container_list); + * Array + * ( + * [0] => "my photos", + * [1] => "my docs" + * ) + * + * + * @param integer $limit restrict results to $limit Containers + * @param string $marker return results greater than $marker + * @return array list of remote Containers + * @throws InvalidResponseException unexpected response + */ + function list_containers($limit=0, $marker=NULL) + { + list($status, $reason, $containers) = + $this->cfs_http->list_containers($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_containers($limit, $marker); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $containers; + } + + /** + * Return array of information about remote Containers + * + * Return a nested array structure of Container info. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * + * $container_info = $conn->list_containers_info(); + * print_r($container_info); + * Array + * ( + * ["my photos"] => + * Array + * ( + * ["bytes"] => 78, + * ["count"] => 2 + * ) + * ["docs"] => + * Array + * ( + * ["bytes"] => 37323, + * ["count"] => 12 + * ) + * ) + * + * + * @param integer $limit restrict results to $limit Containers + * @param string $marker return results greater than $marker + * @return array nested array structure of Container info + * @throws InvalidResponseException unexpected response + */ + function list_containers_info($limit=0, $marker=NULL) + { + list($status, $reason, $container_info) = + $this->cfs_http->list_containers_info($limit, $marker); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_containers_info($limit, $marker); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $container_info; + } + + /** + * Return list of Containers that have been published to the CDN. + * + * Return an array of strings containing the names of published Containers. + * Note that this function returns the list of any Container that has + * ever been CDN-enabled regardless of it's existence in the storage + * system. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_containers = $conn->list_public_containers(); + * print_r($public_containers); + * Array + * ( + * [0] => "images", + * [1] => "css", + * [2] => "javascript" + * ) + * + * + * @param bool $enabled_only Will list all containers ever CDN enabled if * set to false or only currently enabled CDN containers if set to true. * Defaults to false. + * @return array list of published Container names + * @throws InvalidResponseException unexpected response + */ + function list_public_containers($enabled_only=False) + { + list($status, $reason, $containers) = + $this->cfs_http->list_cdn_containers($enabled_only); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_public_containers(); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $containers; + } + + /** + * Set a user-supplied callback function to report download progress + * + * The callback function is used to report incremental progress of a data + * download functions (e.g. $container->list_objects(), $obj->read(), etc). + * The specified function will be periodically called with the number of + * bytes transferred until the entire download is complete. This callback + * function can be useful for implementing "progress bars" for large + * downloads. + * + * The specified callback function should take a single integer parameter. + * + * + * function read_callback($bytes_transferred) { + * print ">> downloaded " . $bytes_transferred . " bytes.\n"; + * # ... do other things ... + * return; + * } + * + * $conn = new CF_Connection($auth_obj); + * $conn->set_read_progress_function("read_callback"); + * print_r($conn->list_containers()); + * + * # output would look like this: + * # + * >> downloaded 10 bytes. + * >> downloaded 11 bytes. + * Array + * ( + * [0] => fuzzy.txt + * [1] => space name + * ) + * + * + * @param string $func_name the name of the user callback function + */ + function set_read_progress_function($func_name) + { + $this->cfs_http->setReadProgressFunc($func_name); + } + + /** + * Set a user-supplied callback function to report upload progress + * + * The callback function is used to report incremental progress of a data + * upload functions (e.g. $obj->write() call). The specified function will + * be periodically called with the number of bytes transferred until the + * entire upload is complete. This callback function can be useful + * for implementing "progress bars" for large uploads/downloads. + * + * The specified callback function should take a single integer parameter. + * + * + * function write_callback($bytes_transferred) { + * print ">> uploaded " . $bytes_transferred . " bytes.\n"; + * # ... do other things ... + * return; + * } + * + * $conn = new CF_Connection($auth_obj); + * $conn->set_write_progress_function("write_callback"); + * $container = $conn->create_container("stuff"); + * $obj = $container->create_object("foo"); + * $obj->write("The callback function will be called during upload."); + * + * # output would look like this: + * # >> uploaded 51 bytes. + * # + * + * + * @param string $func_name the name of the user callback function + */ + function set_write_progress_function($func_name) + { + $this->cfs_http->setWriteProgressFunc($func_name); + } + + /** + * Use the Certificate Authority bundle included with this API + * + * Most versions of PHP with cURL support include an outdated Certificate + * Authority (CA) bundle (the file that lists all valid certificate + * signing authorities). The SSL certificates used by the Cloud Files + * storage system are perfectly valid but have been created/signed by + * a CA not listed in these outdated cURL distributions. + * + * As a work-around, we've included an updated CA bundle obtained + * directly from cURL's web site (http://curl.haxx.se). You can direct + * the API to use this CA bundle by calling this method prior to making + * any remote calls. The best place to use this method is right after + * the CF_Authentication instance has been instantiated. + * + * You can specify your own CA bundle by passing in the full pathname + * to the bundle. You can use the included CA bundle by leaving the + * argument blank. + * + * @param string $path Specify path to CA bundle (default to included) + */ + function ssl_use_cabundle($path=NULL) + { + $this->cfs_http->ssl_use_cabundle($path); + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->cfs_auth = $new_auth; + # $this->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + +/** + * Container operations + * + * Containers are storage compartments where you put your data (objects). + * A container is similar to a directory or folder on a conventional filesystem + * with the exception that they exist in a flat namespace, you can not create + * containers inside of containers. + * + * You also have the option of marking a Container as "public" so that the + * Objects stored in the Container are publicly available via the CDN. + * + * @package php-cloudfiles + */ +class CF_Container +{ + public $cfs_auth; + public $cfs_http; + public $name; + public $object_count; + public $bytes_used; + + public $cdn_enabled; + public $cdn_streaming_uri; + public $cdn_ssl_uri; + public $cdn_uri; + public $cdn_ttl; + public $cdn_log_retention; + public $cdn_acl_user_agent; + public $cdn_acl_referrer; + + /** + * Class constructor + * + * Constructor for Container + * + * @param obj $cfs_auth CF_Authentication instance + * @param obj $cfs_http HTTP connection manager + * @param string $name name of Container + * @param int $count number of Objects stored in this Container + * @param int $bytes number of bytes stored in this Container + * @throws SyntaxException invalid Container name + */ + function __construct(&$cfs_auth, &$cfs_http, $name, $count=0, + $bytes=0, $docdn=True) + { + if (strlen($name) > MAX_CONTAINER_NAME_LEN) { + throw new SyntaxException("Container name exceeds " + . "maximum allowed length."); + } + if (strpos($name, "/") !== False) { + throw new SyntaxException( + "Container names cannot contain a '/' character."); + } + $this->cfs_auth = $cfs_auth; + $this->cfs_http = $cfs_http; + $this->name = $name; + $this->object_count = $count; + $this->bytes_used = $bytes; + $this->cdn_enabled = NULL; + $this->cdn_uri = NULL; + $this->cdn_ssl_uri = NULL; + $this->cdn_streaming_uri = NULL; + $this->cdn_ttl = NULL; + $this->cdn_log_retention = NULL; + $this->cdn_acl_user_agent = NULL; + $this->cdn_acl_referrer = NULL; + if ($this->cfs_http->getCDNMUrl() != NULL && $docdn) { + $this->_cdn_initialize(); + } + } + + /** + * String representation of Container + * + * Pretty print the Container instance. + * + * @return string Container details + */ + function __toString() + { + $me = sprintf("name: %s, count: %.0f, bytes: %.0f", + $this->name, $this->object_count, $this->bytes_used); + if ($this->cfs_http->getCDNMUrl() != NULL) { + $me .= sprintf(", cdn: %s, cdn uri: %s, cdn ttl: %.0f, logs retention: %s", + $this->is_public() ? "Yes" : "No", + $this->cdn_uri, $this->cdn_ttl, + $this->cdn_log_retention ? "Yes" : "No" + ); + + if ($this->cdn_acl_user_agent != NULL) { + $me .= ", cdn acl user agent: " . $this->cdn_acl_user_agent; + } + + if ($this->cdn_acl_referrer != NULL) { + $me .= ", cdn acl referrer: " . $this->cdn_acl_referrer; + } + + + } + return $me; + } + + /** + * Enable Container content to be served via CDN or modify CDN attributes + * + * Either enable this Container's content to be served via CDN or + * adjust its CDN attributes. This Container will always return the + * same CDN-enabled URI each time it is toggled public/private/public. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->create_container("public"); + * + * # CDN-enable the container and set it's TTL for a month + * # + * $public_container->make_public(86400/2); # 12 hours (86400 seconds/day) + * + * + * @param int $ttl the time in seconds content will be cached in the CDN + * @returns string the CDN enabled Container's URI + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function make_public($ttl=86400) + { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + if ($this->cdn_uri != NULL) { + # previously published, assume we're setting new attributes + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->update_cdn_container($this->name,$ttl, + $this->cdn_log_retention, + $this->cdn_acl_user_agent, + $this->cdn_acl_referrer); + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_public($ttl); + #} + if ($status == 404) { + # this instance _thinks_ the container was published, but the + # cdn management system thinks otherwise - try again with a PUT + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->add_cdn_container($this->name,$ttl); + + } + } else { + # publish it for first time + list($status, $reason, $cdn_uri, $cdn_ssl_uri) = + $this->cfs_http->add_cdn_container($this->name,$ttl); + } + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_public($ttl); + #} + if (!in_array($status, array(201,202))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = True; + $this->cdn_ttl = $ttl; + $this->cdn_ssl_uri = $cdn_ssl_uri; + $this->cdn_uri = $cdn_uri; + $this->cdn_log_retention = False; + $this->cdn_acl_user_agent = ""; + $this->cdn_acl_referrer = ""; + return $this->cdn_uri; + } + /** + * Purge Containers objects from CDN Cache. + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * $container = $conn->get_container("cdn_enabled"); + * $container->purge_from_cdn("user@domain.com"); + * # or + * $container->purge_from_cdn(); + * # or + * $container->purge_from_cdn("user1@domain.com,user2@domain.com"); + * @returns boolean True if successful + * @throws CDNNotEnabledException if CDN Is not enabled on this connection + * @throws InvalidResponseException if the response expected is not returned + */ + function purge_from_cdn($email=null) + { + if (!$this->cfs_http->getCDNMUrl()) + { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + $status = $this->cfs_http->purge_from_cdn($this->name, $email); + if ($status < 199 or $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return True; + } + /** + * Enable ACL restriction by User Agent for this container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable ACL by Referrer + * $public_container->acl_referrer("Mozilla"); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function acl_user_agent($cdn_acl_user_agent="") { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $this->cdn_log_retention, + $cdn_acl_user_agent, + $this->cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_acl_user_agent = $cdn_acl_user_agent; + return True; + } + + /** + * Enable ACL restriction by referer for this container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable Referrer + * $public_container->acl_referrer("http://www.example.com/gallery.php"); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function acl_referrer($cdn_acl_referrer="") { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $this->cdn_log_retention, + $this->cdn_acl_user_agent, + $cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_acl_referrer = $cdn_acl_referrer; + return True; + } + + /** + * Enable log retention for this CDN container. + * + * Enable CDN log retention on the container. If enabled logs will + * be periodically (at unpredictable intervals) compressed and + * uploaded to a ".CDN_ACCESS_LOGS" container in the form of + * "container_name.YYYYMMDDHH-XXXX.gz". Requires CDN be enabled on + * the account. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Enable logs retention. + * $public_container->log_retention(True); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function log_retention($cdn_log_retention=False) { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = + $this->cfs_http->update_cdn_container($this->name, + $this->cdn_ttl, + $cdn_log_retention, + $this->cdn_acl_user_agent, + $this->cdn_acl_referrer + ); + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_log_retention = $cdn_log_retention; + return True; + } + + /** + * Disable the CDN sharing for this container + * + * Use this method to disallow distribution into the CDN of this Container's + * content. + * + * NOTE: Any content already cached in the CDN will continue to be served + * from its cache until the TTL expiration transpires. The default + * TTL is typically one day, so "privatizing" the Container will take + * up to 24 hours before the content is purged from the CDN cache. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Disable CDN accessability + * # ... still cached up to a month based on previous example + * # + * $public_container->make_private(); + * + * + * @returns boolean True if successful + * @throws CDNNotEnabledException CDN functionality not returned during auth + * @throws AuthenticationException if auth token is not valid/expired + * @throws InvalidResponseException unexpected response + */ + function make_private() + { + if ($this->cfs_http->getCDNMUrl() == NULL) { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + list($status,$reason) = $this->cfs_http->remove_cdn_container($this->name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->make_private(); + #} + if (!in_array($status, array(202,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = False; + $this->cdn_ttl = NULL; + $this->cdn_uri = NULL; + $this->cdn_ssl_uri = NULL; + $this->cdn_streaming_uri - NULL; + $this->cdn_log_retention = NULL; + $this->cdn_acl_user_agent = NULL; + $this->cdn_acl_referrer = NULL; + return True; + } + + /** + * Check if this Container is being publicly served via CDN + * + * Use this method to determine if the Container's content is currently + * available through the CDN. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # Display CDN accessability + * # + * $public_container->is_public() ? print "Yes" : print "No"; + * + * + * @returns boolean True if enabled, False otherwise + */ + function is_public() + { + return $this->cdn_enabled == True ? True : False; + } + + /** + * Create a new remote storage Object + * + * Return a new Object instance. If the remote storage Object exists, + * the instance's attributes are populated. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # This creates a local instance of a storage object but only creates + * # it in the storage system when the object's write() method is called. + * # + * $pic = $public_container->create_object("baby.jpg"); + * + * + * @param string $obj_name name of storage Object + * @return obj CF_Object instance + */ + function create_object($obj_name=NULL) + { + return new CF_Object($this, $obj_name); + } + + /** + * Return an Object instance for the remote storage Object + * + * Given a name, return a Object instance representing the + * remote storage object. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $public_container = $conn->get_container("public"); + * + * # This call only fetches header information and not the content of + * # the storage object. Use the Object's read() or stream() methods + * # to obtain the object's data. + * # + * $pic = $public_container->get_object("baby.jpg"); + * + * + * @param string $obj_name name of storage Object + * @return obj CF_Object instance + */ + function get_object($obj_name=NULL) + { + return new CF_Object($this, $obj_name, True); + } + + /** + * Return a list of Objects + * + * Return an array of strings listing the Object names in this Container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $images = $conn->get_container("my photos"); + * + * # Grab the list of all storage objects + * # + * $all_objects = $images->list_objects(); + * + * # Grab subsets of all storage objects + * # + * $first_ten = $images->list_objects(10); + * + * # Note the use of the previous result's last object name being + * # used as the 'marker' parameter to fetch the next 10 objects + * # + * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]); + * + * # Grab images starting with "birthday_party" and default limit/marker + * # to match all photos with that prefix + * # + * $prefixed = $images->list_objects(0, NULL, "birthday"); + * + * # Assuming you have created the appropriate directory marker Objects, + * # you can traverse your pseudo-hierarchical containers + * # with the "path" argument. + * # + * $animals = $images->list_objects(0,NULL,NULL,"pictures/animals"); + * $dogs = $images->list_objects(0,NULL,NULL,"pictures/animals/dogs"); + * + * + * @param int $limit optional only return $limit names + * @param int $marker optional subset of names starting at $marker + * @param string $prefix optional Objects whose names begin with $prefix + * @param string $path optional only return results under "pathname" + * @return array array of strings + * @throws InvalidResponseException unexpected response + */ + function list_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL) + { + list($status, $reason, $obj_list) = + $this->cfs_http->list_objects($this->name, $limit, + $marker, $prefix, $path); + #if ($status == 401 && $this->_re_auth()) { + # return $this->list_objects($limit, $marker, $prefix, $path); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return $obj_list; + } + + /** + * Return an array of Objects + * + * Return an array of Object instances in this Container. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $images = $conn->get_container("my photos"); + * + * # Grab the list of all storage objects + * # + * $all_objects = $images->get_objects(); + * + * # Grab subsets of all storage objects + * # + * $first_ten = $images->get_objects(10); + * + * # Note the use of the previous result's last object name being + * # used as the 'marker' parameter to fetch the next 10 objects + * # + * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]); + * + * # Grab images starting with "birthday_party" and default limit/marker + * # to match all photos with that prefix + * # + * $prefixed = $images->get_objects(0, NULL, "birthday"); + * + * # Assuming you have created the appropriate directory marker Objects, + * # you can traverse your pseudo-hierarchical containers + * # with the "path" argument. + * # + * $animals = $images->get_objects(0,NULL,NULL,"pictures/animals"); + * $dogs = $images->get_objects(0,NULL,NULL,"pictures/animals/dogs"); + * + * + * @param int $limit optional only return $limit names + * @param int $marker optional subset of names starting at $marker + * @param string $prefix optional Objects whose names begin with $prefix + * @param string $path optional only return results under "pathname" + * @return array array of strings + * @throws InvalidResponseException unexpected response + */ + function get_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL) + { + list($status, $reason, $obj_array) = + $this->cfs_http->get_objects($this->name, $limit, + $marker, $prefix, $path); + #if ($status == 401 && $this->_re_auth()) { + # return $this->get_objects($limit, $marker, $prefix, $path); + #} + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $objects = array(); + foreach ($obj_array as $obj) { + $tmp = new CF_Object($this, $obj["name"], False, False); + $tmp->content_type = $obj["content_type"]; + $tmp->content_length = (float) $obj["bytes"]; + $tmp->set_etag($obj["hash"]); + $tmp->last_modified = $obj["last_modified"]; + $objects[] = $tmp; + } + return $objects; + } + + /** + * Copy a remote storage Object to a target Container + * + * Given an Object instance or name and a target Container instance or name, copy copies the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Copy specific object + * # + * $images->copy_object_to("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to copy + * @param obj $container_target name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully copied + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function copy_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + if ($dest_obj_name === NULL) { + $dest_obj_name = $obj_name; + } + + $container_name_target = NULL; + if (is_object($container_target)) { + if (get_class($container_target) == "CF_Container") { + $container_name_target = $container_target->name; + } + } + if (is_string($container_target)) { + $container_name_target = $container_target; + } + if (!$container_name_target) { + throw new SyntaxException("Container name target not set."); + } + + $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$this->name,$container_name_target,$metadata,$headers); + if ($status == 404) { + $m = "Specified object '".$this->name."/".$obj_name; + $m.= "' did not exist as source to copy from or '".$container_name_target."' did not exist as target to copy to."; + throw new NoSuchObjectException($m); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return true; + } + + /** + * Copy a remote storage Object from a source Container + * + * Given an Object instance or name and a source Container instance or name, copy copies the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Copy specific object + * # + * $images->copy_object_from("disco_dancing.jpg","container_source"); + * + * + * @param obj $obj name or instance of Object to copy + * @param obj $container_source name or instance of source Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully copied + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function copy_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + if ($dest_obj_name === NULL) { + $dest_obj_name = $obj_name; + } + + $container_name_source = NULL; + if (is_object($container_source)) { + if (get_class($container_source) == "CF_Container") { + $container_name_source = $container_source->name; + } + } + if (is_string($container_source)) { + $container_name_source = $container_source; + } + if (!$container_name_source) { + throw new SyntaxException("Container name source not set."); + } + + $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$container_name_source,$this->name,$metadata,$headers); + if ($status == 404) { + $m = "Specified object '".$container_name_source."/".$obj_name; + $m.= "' did not exist as source to copy from or '".$this->name."/".$obj_name."' did not exist as target to copy to."; + throw new NoSuchObjectException($m); + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + + return true; + } + + /** + * Move a remote storage Object to a target Container + * + * Given an Object instance or name and a target Container instance or name, move copies the remote Object + * and all associated metadata and deletes the source Object afterwards + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Move specific object + * # + * $images->move_object_to("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to move + * @param obj $container_target name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully moved + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function move_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $retVal = false; + + if(self::copy_object_to($obj,$container_target,$dest_obj_name,$metadata,$headers)) { + $retVal = self::delete_object($obj,$this->name); + } + + return $retVal; + } + + /** + * Move a remote storage Object from a source Container + * + * Given an Object instance or name and a source Container instance or name, move copies the remote Object + * and all associated metadata and deletes the source Object afterwards + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Move specific object + * # + * $images->move_object_from("disco_dancing.jpg","container_target"); + * + * + * @param obj $obj name or instance of Object to move + * @param obj $container_source name or instance of target Container + * @param string $dest_obj_name name of target object (optional - uses source name if omitted) + * @param array $metadata metadata array for new object (optional) + * @param array $headers header fields array for the new object (optional) + * @return boolean true if successfully moved + * @throws SyntaxException invalid Object/Container name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function move_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL) + { + $retVal = false; + + if(self::copy_object_from($obj,$container_source,$dest_obj_name,$metadata,$headers)) { + $retVal = self::delete_object($obj,$container_source); + } + + return $retVal; + } + + /** + * Delete a remote storage Object + * + * Given an Object instance or name, permanently remove the remote Object + * and all associated metadata. + * + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * + * $images = $conn->get_container("my photos"); + * + * # Delete specific object + * # + * $images->delete_object("disco_dancing.jpg"); + * + * + * @param obj $obj name or instance of Object to delete + * @param obj $container name or instance of Container in which the object resides (optional) + * @return boolean True if successfully removed + * @throws SyntaxException invalid Object name + * @throws NoSuchObjectException remote Object does not exist + * @throws InvalidResponseException unexpected response + */ + function delete_object($obj,$container=NULL) + { + $obj_name = NULL; + if (is_object($obj)) { + if (get_class($obj) == "CF_Object") { + $obj_name = $obj->name; + } + } + if (is_string($obj)) { + $obj_name = $obj; + } + if (!$obj_name) { + throw new SyntaxException("Object name not set."); + } + + $container_name = NULL; + + if($container === NULL) { + $container_name = $this->name; + } + else { + if (is_object($container)) { + if (get_class($container) == "CF_Container") { + $container_name = $container->name; + } + } + if (is_string($container)) { + $container_name = $container; + } + if (!$container_name) { + throw new SyntaxException("Container name source not set."); + } + } + + $status = $this->cfs_http->delete_object($container_name, $obj_name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->delete_object($obj); + #} + if ($status == 404) { + $m = "Specified object '".$container_name."/".$obj_name; + $m.= "' did not exist to delete."; + throw new NoSuchObjectException($m); + } + if ($status != 204) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + return True; + } + + /** + * Helper function to create "path" elements for a given Object name + * + * Given an Object whos name contains '/' path separators, this function + * will create the "directory marker" Objects of one byte with the + * Content-Type of "application/directory". + * + * It assumes the last element of the full path is the "real" Object + * and does NOT create a remote storage Object for that last element. + */ + function create_paths($path_name) + { + if ($path_name[0] == '/') { + $path_name = mb_substr($path_name, 0, 1); + } + $elements = explode('/', $path_name, -1); + $build_path = ""; + foreach ($elements as $idx => $val) { + if (!$build_path) { + $build_path = $val; + } else { + $build_path .= "/" . $val; + } + $obj = new CF_Object($this, $build_path); + $obj->content_type = "application/directory"; + $obj->write(".", 1); + } + } + + /** + * Internal method to grab CDN/Container info if appropriate to do so + * + * @throws InvalidResponseException unexpected response + */ + private function _cdn_initialize() + { + list($status, $reason, $cdn_enabled, $cdn_ssl_uri, $cdn_streaming_uri, $cdn_uri, $cdn_ttl, + $cdn_log_retention, $cdn_acl_user_agent, $cdn_acl_referrer) = + $this->cfs_http->head_cdn_container($this->name); + #if ($status == 401 && $this->_re_auth()) { + # return $this->_cdn_initialize(); + #} + if (!in_array($status, array(204,404))) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->cfs_http->get_error()); + } + $this->cdn_enabled = $cdn_enabled; + $this->cdn_streaming_uri = $cdn_streaming_uri; + $this->cdn_ssl_uri = $cdn_ssl_uri; + $this->cdn_uri = $cdn_uri; + $this->cdn_ttl = $cdn_ttl; + $this->cdn_log_retention = $cdn_log_retention; + $this->cdn_acl_user_agent = $cdn_acl_user_agent; + $this->cdn_acl_referrer = $cdn_acl_referrer; + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->cfs_auth = $new_auth; + # $this->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + + +/** + * Object operations + * + * An Object is analogous to a file on a conventional filesystem. You can + * read data from, or write data to your Objects. You can also associate + * arbitrary metadata with them. + * + * @package php-cloudfiles + */ +class CF_Object +{ + public $container; + public $name; + public $last_modified; + public $content_type; + public $content_length; + public $metadata; + public $headers; + public $manifest; + private $etag; + + /** + * Class constructor + * + * @param obj $container CF_Container instance + * @param string $name name of Object + * @param boolean $force_exists if set, throw an error if Object doesn't exist + */ + function __construct(&$container, $name, $force_exists=False, $dohead=True) + { + if ($name[0] == "/") { + $r = "Object name '".$name; + $r .= "' cannot contain begin with a '/' character."; + throw new SyntaxException($r); + } + if (strlen($name) > MAX_OBJECT_NAME_LEN) { + throw new SyntaxException("Object name exceeds " + . "maximum allowed length."); + } + $this->container = $container; + $this->name = $name; + $this->etag = NULL; + $this->_etag_override = False; + $this->last_modified = NULL; + $this->content_type = NULL; + $this->content_length = 0; + $this->metadata = array(); + $this->headers = array(); + $this->manifest = NULL; + if ($dohead) { + if (!$this->_initialize() && $force_exists) { + throw new NoSuchObjectException("No such object '".$name."'"); + } + } + } + + /** + * String representation of Object + * + * Pretty print the Object's location and name + * + * @return string Object information + */ + function __toString() + { + return $this->container->name . "/" . $this->name; + } + + /** + * Internal check to get the proper mimetype. + * + * This function would go over the available PHP methods to get + * the MIME type. + * + * By default it will try to use the PHP fileinfo library which is + * available from PHP 5.3 or as an PECL extension + * (http://pecl.php.net/package/Fileinfo). + * + * It will get the magic file by default from the system wide file + * which is usually available in /usr/share/magic on Unix or try + * to use the file specified in the source directory of the API + * (share directory). + * + * if fileinfo is not available it will try to use the internal + * mime_content_type function. + * + * @param string $handle name of file or buffer to guess the type from + * @return boolean True if successful + * @throws BadContentTypeException + */ + function _guess_content_type($handle) { + if ($this->content_type) + return; + +// if (function_exists("finfo_open")) { +// $local_magic = dirname(__FILE__) . "/share/magic"; +// $finfo = @finfo_open(FILEINFO_MIME, $local_magic); +// +// if (!$finfo) +// $finfo = @finfo_open(FILEINFO_MIME); +// +// if ($finfo) { +// +// if (is_file((string)$handle)) +// $ct = @finfo_file($finfo, $handle); +// else +// $ct = @finfo_buffer($finfo, $handle); +// +// /* PHP 5.3 fileinfo display extra information like +// charset so we remove everything after the ; since +// we are not into that stuff */ +// if ($ct) { +// $extra_content_type_info = strpos($ct, "; "); +// if ($extra_content_type_info) +// $ct = substr($ct, 0, $extra_content_type_info); +// } +// +// if ($ct && $ct != 'application/octet-stream') +// $this->content_type = $ct; +// +// @finfo_close($finfo); +// } +// } +// +// if (!$this->content_type && (string)is_file($handle) && function_exists("mime_content_type")) { +// $this->content_type = @mime_content_type($handle); +// } + + //use OC's mimetype detection for files + if(is_file($handle)){ + $this->content_type=OC_Helper::getMimeType($handle); + }else{ + $this->content_type=OC_Helper::getStringMimeType($handle); + } + + if (!$this->content_type) { + throw new BadContentTypeException("Required Content-Type not set"); + } + return True; + } + + /** + * String representation of the Object's public URI + * + * A string representing the Object's public URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public URI or NULL + */ + function public_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_uri . "/" . $this->name; + } + return NULL; + } + + /** + * String representation of the Object's public SSL URI + * + * A string representing the Object's public SSL URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN SSL URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public SSL URI or NULL + */ + function public_ssl_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_ssl_uri . "/" . $this->name; + } + return NULL; + } + /** + * String representation of the Object's public Streaming URI + * + * A string representing the Object's public Streaming URI assuming that it's + * parent Container is CDN-enabled. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Print out the Object's CDN Streaming URI (if it has one) in an HTML img-tag + * # + * print "\n"; + * + * + * @return string Object's public Streaming URI or NULL + */ + function public_streaming_uri() + { + if ($this->container->cdn_enabled) { + return $this->container->cdn_streaming_uri . "/" . $this->name; + } + return NULL; + } + + /** + * Read the remote Object's data + * + * Returns the Object's data. This is useful for smaller Objects such + * as images or office documents. Object's with larger content should use + * the stream() method below. + * + * Pass in $hdrs array to set specific custom HTTP headers such as + * If-Match, If-None-Match, If-Modified-Since, Range, etc. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * $data = $doc->read(); # read image content into a string variable + * print $data; + * + * # Or see stream() below for a different example. + * # + * + * + * @param array $hdrs user-defined headers (Range, If-Match, etc.) + * @return string Object's data + * @throws InvalidResponseException unexpected response + */ + function read($hdrs=array()) + { + list($status, $reason, $data) = + $this->container->cfs_http->get_object_to_string($this, $hdrs); + #if ($status == 401 && $this->_re_auth()) { + # return $this->read($hdrs); + #} + if (($status < 200) || ($status > 299 + && $status != 412 && $status != 304)) { + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + return $data; + } + + /** + * Streaming read of Object's data + * + * Given an open PHP resource (see PHP's fopen() method), fetch the Object's + * data and write it to the open resource handle. This is useful for + * streaming an Object's content to the browser (videos, images) or for + * fetching content to a local file. + * + * Pass in $hdrs array to set specific custom HTTP headers such as + * If-Match, If-None-Match, If-Modified-Since, Range, etc. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Assuming this is a web script to display the README to the + * # user's browser: + * # + * get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * // Hand it back to user's browser with appropriate content-type + * // + * header("Content-Type: " . $doc->content_type); + * $output = fopen("php://output", "w"); + * $doc->stream($output); # stream object content to PHP's output buffer + * fclose($output); + * ?> + * + * # See read() above for a more simple example. + * # + * + * + * @param resource $fp open resource for writing data to + * @param array $hdrs user-defined headers (Range, If-Match, etc.) + * @return string Object's data + * @throws InvalidResponseException unexpected response + */ + function stream(&$fp, $hdrs=array()) + { + list($status, $reason) = + $this->container->cfs_http->get_object_to_stream($this,$fp,$hdrs); + #if ($status == 401 && $this->_re_auth()) { + # return $this->stream($fp, $hdrs); + #} + if (($status < 200) || ($status > 299 + && $status != 412 && $status != 304)) { + throw new InvalidResponseException("Invalid response (".$status."): " + .$reason); + } + return True; + } + + /** + * Store new Object metadata + * + * Write's an Object's metadata to the remote Object. This will overwrite + * an prior Object metadata. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Define new metadata for the object + * # + * $doc->metadata = array( + * "Author" => "EJ", + * "Subject" => "How to use the PHP tests", + * "Version" => "1.2.2" + * ); + * + * # Define additional headers for the object + * # + * $doc->headers = array( + * "Content-Disposition" => "attachment", + * ); + * + * # Push the new metadata up to the storage system + * # + * $doc->sync_metadata(); + * + * + * @return boolean True if successful, False otherwise + * @throws InvalidResponseException unexpected response + */ + function sync_metadata() + { + if (!empty($this->metadata) || !empty($this->headers) || $this->manifest) { + $status = $this->container->cfs_http->update_object($this); + #if ($status == 401 && $this->_re_auth()) { + # return $this->sync_metadata(); + #} + if ($status != 202) { + throw new InvalidResponseException("Invalid response (" + .$status."): ".$this->container->cfs_http->get_error()); + } + return True; + } + return False; + } + /** + * Store new Object manifest + * + * Write's an Object's manifest to the remote Object. This will overwrite + * an prior Object manifest. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Define new manifest for the object + * # + * $doc->manifest = "container/prefix"; + * + * # Push the new manifest up to the storage system + * # + * $doc->sync_manifest(); + * + * + * @return boolean True if successful, False otherwise + * @throws InvalidResponseException unexpected response + */ + + function sync_manifest() + { + return $this->sync_metadata(); + } + /** + * Upload Object's data to Cloud Files + * + * Write data to the remote Object. The $data argument can either be a + * PHP resource open for reading (see PHP's fopen() method) or an in-memory + * variable. If passing in a PHP resource, you must also include the $bytes + * parameter. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Upload placeholder text in my README + * # + * $doc->write("This is just placeholder text for now..."); + * + * + * @param string|resource $data string or open resource + * @param float $bytes amount of data to upload (required for resources) + * @param boolean $verify generate, send, and compare MD5 checksums + * @return boolean True when data uploaded successfully + * @throws SyntaxException missing required parameters + * @throws BadContentTypeException if no Content-Type was/could be set + * @throws MisMatchedChecksumException $verify is set and checksums unequal + * @throws InvalidResponseException unexpected response + */ + function write($data=NULL, $bytes=0, $verify=True) + { + if (!$data && !is_string($data)) { + throw new SyntaxException("Missing data source."); + } + if ($bytes > MAX_OBJECT_SIZE) { + throw new SyntaxException("Bytes exceeds maximum object size."); + } + if ($verify) { + if (!$this->_etag_override) { + $this->etag = $this->compute_md5sum($data); + } + } else { + $this->etag = NULL; + } + + $close_fh = False; + if (!is_resource($data)) { + # A hack to treat string data as a file handle. php://memory feels + # like a better option, but it seems to break on Windows so use + # a temporary file instead. + # + $fp = fopen("php://temp", "wb+"); + #$fp = fopen("php://memory", "wb+"); + fwrite($fp, $data, strlen($data)); + rewind($fp); + $close_fh = True; + $this->content_length = (float) strlen($data); + if ($this->content_length > MAX_OBJECT_SIZE) { + throw new SyntaxException("Data exceeds maximum object size"); + } + $ct_data = substr($data, 0, 64); + } else { + $this->content_length = $bytes; + $fp = $data; + $ct_data = fread($data, 64); + rewind($data); + } + + $this->_guess_content_type($ct_data); + + list($status, $reason, $etag) = + $this->container->cfs_http->put_object($this, $fp); + #if ($status == 401 && $this->_re_auth()) { + # return $this->write($data, $bytes, $verify); + #} + if ($status == 412) { + if ($close_fh) { fclose($fp); } + throw new SyntaxException("Missing Content-Type header"); + } + if ($status == 422) { + if ($close_fh) { fclose($fp); } + throw new MisMatchedChecksumException( + "Supplied and computed checksums do not match."); + } + if ($status != 201) { + if ($close_fh) { fclose($fp); } + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + if (!$verify) { + $this->etag = $etag; + } + if ($close_fh) { fclose($fp); } + return True; + } + + /** + * Upload Object data from local filename + * + * This is a convenience function to upload the data from a local file. A + * True value for $verify will cause the method to compute the Object's MD5 + * checksum prior to uploading. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * # Upload my local README's content + * # + * $doc->load_from_filename("/home/ej/cloudfiles/readme"); + * + * + * @param string $filename full path to local file + * @param boolean $verify enable local/remote MD5 checksum validation + * @return boolean True if data uploaded successfully + * @throws SyntaxException missing required parameters + * @throws BadContentTypeException if no Content-Type was/could be set + * @throws MisMatchedChecksumException $verify is set and checksums unequal + * @throws InvalidResponseException unexpected response + * @throws IOException error opening file + */ + function load_from_filename($filename, $verify=True) + { + $fp = @fopen($filename, "r"); + if (!$fp) { + throw new IOException("Could not open file for reading: ".$filename); + } + + clearstatcache(); + + $size = (float) sprintf("%u", filesize($filename)); + if ($size > MAX_OBJECT_SIZE) { + throw new SyntaxException("File size exceeds maximum object size."); + } + + $this->_guess_content_type($filename); + + $this->write($fp, $size, $verify); + fclose($fp); + return True; + } + + /** + * Save Object's data to local filename + * + * Given a local filename, the Object's data will be written to the newly + * created file. + * + * Example: + * + * # ... authentication/connection/container code excluded + * # ... see previous examples + * + * # Whoops! I deleted my local README, let me download/save it + * # + * $my_docs = $conn->get_container("documents"); + * $doc = $my_docs->get_object("README"); + * + * $doc->save_to_filename("/home/ej/cloudfiles/readme.restored"); + * + * + * @param string $filename name of local file to write data to + * @return boolean True if successful + * @throws IOException error opening file + * @throws InvalidResponseException unexpected response + */ + function save_to_filename($filename) + { + $fp = @fopen($filename, "wb"); + if (!$fp) { + throw new IOException("Could not open file for writing: ".$filename); + } + $result = $this->stream($fp); + fclose($fp); + return $result; + } + /** + * Purge this Object from CDN Cache. + * Example: + * + * # ... authentication code excluded (see previous examples) ... + * # + * $conn = new CF_Authentication($auth); + * $container = $conn->get_container("cdn_enabled"); + * $obj = $container->get_object("object"); + * $obj->purge_from_cdn("user@domain.com"); + * # or + * $obj->purge_from_cdn(); + * # or + * $obj->purge_from_cdn("user1@domain.com,user2@domain.com"); + * @returns boolean True if successful + * @throws CDNNotEnabledException if CDN Is not enabled on this connection + * @throws InvalidResponseException if the response expected is not returned + */ + function purge_from_cdn($email=null) + { + if (!$this->container->cfs_http->getCDNMUrl()) + { + throw new CDNNotEnabledException( + "Authentication response did not indicate CDN availability"); + } + $status = $this->container->cfs_http->purge_from_cdn($this->container->name . "/" . $this->name, $email); + if ($status < 199 or $status > 299) { + throw new InvalidResponseException( + "Invalid response (".$status."): ".$this->container->cfs_http->get_error()); + } + return True; + } + + /** + * Set Object's MD5 checksum + * + * Manually set the Object's ETag. Including the ETag is mandatory for + * Cloud Files to perform end-to-end verification. Omitting the ETag forces + * the user to handle any data integrity checks. + * + * @param string $etag MD5 checksum hexidecimal string + */ + function set_etag($etag) + { + $this->etag = $etag; + $this->_etag_override = True; + } + + /** + * Object's MD5 checksum + * + * Accessor method for reading Object's private ETag attribute. + * + * @return string MD5 checksum hexidecimal string + */ + function getETag() + { + return $this->etag; + } + + /** + * Compute the MD5 checksum + * + * Calculate the MD5 checksum on either a PHP resource or data. The argument + * may either be a local filename, open resource for reading, or a string. + * + * WARNING: if you are uploading a big file over a stream + * it could get very slow to compute the md5 you probably want to + * set the $verify parameter to False in the write() method and + * compute yourself the md5 before if you have it. + * + * @param filename|obj|string $data filename, open resource, or string + * @return string MD5 checksum hexidecimal string + */ + function compute_md5sum(&$data) + { + + if (function_exists("hash_init") && is_resource($data)) { + $ctx = hash_init('md5'); + while (!feof($data)) { + $buffer = fgets($data, 65536); + hash_update($ctx, $buffer); + } + $md5 = hash_final($ctx, false); + rewind($data); + } elseif ((string)is_file($data)) { + $md5 = md5_file($data); + } else { + $md5 = md5($data); + } + return $md5; + } + + /** + * PRIVATE: fetch information about the remote Object if it exists + */ + private function _initialize() + { + list($status, $reason, $etag, $last_modified, $content_type, + $content_length, $metadata, $manifest, $headers) = + $this->container->cfs_http->head_object($this); + #if ($status == 401 && $this->_re_auth()) { + # return $this->_initialize(); + #} + if ($status == 404) { + return False; + } + if ($status < 200 || $status > 299) { + throw new InvalidResponseException("Invalid response (".$status."): " + . $this->container->cfs_http->get_error()); + } + $this->etag = $etag; + $this->last_modified = $last_modified; + $this->content_type = $content_type; + $this->content_length = $content_length; + $this->metadata = $metadata; + $this->headers = $headers; + $this->manifest = $manifest; + return True; + } + + #private function _re_auth() + #{ + # $new_auth = new CF_Authentication( + # $this->cfs_auth->username, + # $this->cfs_auth->api_key, + # $this->cfs_auth->auth_host, + # $this->cfs_auth->account); + # $new_auth->authenticate(); + # $this->container->cfs_auth = $new_auth; + # $this->container->cfs_http->setCFAuth($this->cfs_auth); + # return True; + #} +} + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/3rdparty/php-cloudfiles/cloudfiles_exceptions.php b/3rdparty/php-cloudfiles/cloudfiles_exceptions.php new file mode 100644 index 0000000000..5624d6b863 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles_exceptions.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles-exceptions + */ + +/** + * Custom Exceptions for the CloudFiles API + * @package php-cloudfiles-exceptions + */ +class SyntaxException extends Exception { } +class AuthenticationException extends Exception { } +class InvalidResponseException extends Exception { } +class NonEmptyContainerException extends Exception { } +class NoSuchObjectException extends Exception { } +class NoSuchContainerException extends Exception { } +class NoSuchAccountException extends Exception { } +class MisMatchedChecksumException extends Exception { } +class IOException extends Exception { } +class CDNNotEnabledException extends Exception { } +class BadContentTypeException extends Exception { } +class InvalidUTF8Exception extends Exception { } +class ConnectionNotOpenException extends Exception { } + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/3rdparty/php-cloudfiles/cloudfiles_http.php b/3rdparty/php-cloudfiles/cloudfiles_http.php new file mode 100644 index 0000000000..0e5d9717e8 --- /dev/null +++ b/3rdparty/php-cloudfiles/cloudfiles_http.php @@ -0,0 +1,1488 @@ + + * @copyright Copyright (c) 2008, Rackspace US, Inc. + * @package php-cloudfiles-http + */ + +/** + */ +require_once("cloudfiles_exceptions.php"); + +define("PHP_CF_VERSION", "1.7.10"); +define("USER_AGENT", sprintf("PHP-CloudFiles/%s", PHP_CF_VERSION)); +define("MAX_HEADER_NAME_LEN", 128); +define("MAX_HEADER_VALUE_LEN", 256); +define("ACCOUNT_CONTAINER_COUNT", "X-Account-Container-Count"); +define("ACCOUNT_BYTES_USED", "X-Account-Bytes-Used"); +define("CONTAINER_OBJ_COUNT", "X-Container-Object-Count"); +define("CONTAINER_BYTES_USED", "X-Container-Bytes-Used"); +define("MANIFEST_HEADER", "X-Object-Manifest"); +define("METADATA_HEADER_PREFIX", "X-Object-Meta-"); +define("CONTENT_HEADER_PREFIX", "Content-"); +define("ACCESS_CONTROL_HEADER_PREFIX", "Access-Control-"); +define("ORIGIN_HEADER", "Origin"); +define("CDN_URI", "X-CDN-URI"); +define("CDN_SSL_URI", "X-CDN-SSL-URI"); +define("CDN_STREAMING_URI", "X-CDN-Streaming-URI"); +define("CDN_ENABLED", "X-CDN-Enabled"); +define("CDN_LOG_RETENTION", "X-Log-Retention"); +define("CDN_ACL_USER_AGENT", "X-User-Agent-ACL"); +define("CDN_ACL_REFERRER", "X-Referrer-ACL"); +define("CDN_TTL", "X-TTL"); +define("CDNM_URL", "X-CDN-Management-Url"); +define("STORAGE_URL", "X-Storage-Url"); +define("AUTH_TOKEN", "X-Auth-Token"); +define("AUTH_USER_HEADER", "X-Auth-User"); +define("AUTH_KEY_HEADER", "X-Auth-Key"); +define("AUTH_USER_HEADER_LEGACY", "X-Storage-User"); +define("AUTH_KEY_HEADER_LEGACY", "X-Storage-Pass"); +define("AUTH_TOKEN_LEGACY", "X-Storage-Token"); +define("CDN_EMAIL", "X-Purge-Email"); +define("DESTINATION", "Destination"); +define("ETAG_HEADER", "ETag"); +define("LAST_MODIFIED_HEADER", "Last-Modified"); +define("CONTENT_TYPE_HEADER", "Content-Type"); +define("CONTENT_LENGTH_HEADER", "Content-Length"); +define("USER_AGENT_HEADER", "User-Agent"); + +/** + * HTTP/cURL wrapper for Cloud Files + * + * This class should not be used directly. It's only purpose is to abstract + * out the HTTP communication from the main API. + * + * @package php-cloudfiles-http + */ +class CF_Http +{ + private $error_str; + private $dbug; + private $cabundle_path; + private $api_version; + + # Authentication instance variables + # + private $storage_url; + private $cdnm_url; + private $auth_token; + + # Request/response variables + # + private $response_status; + private $response_reason; + private $connections; + + # Variables used for content/header callbacks + # + private $_user_read_progress_callback_func; + private $_user_write_progress_callback_func; + private $_write_callback_type; + private $_text_list; + private $_account_container_count; + private $_account_bytes_used; + private $_container_object_count; + private $_container_bytes_used; + private $_obj_etag; + private $_obj_last_modified; + private $_obj_content_type; + private $_obj_content_length; + private $_obj_metadata; + private $_obj_headers; + private $_obj_manifest; + private $_obj_write_resource; + private $_obj_write_string; + private $_cdn_enabled; + private $_cdn_ssl_uri; + private $_cdn_streaming_uri; + private $_cdn_uri; + private $_cdn_ttl; + private $_cdn_log_retention; + private $_cdn_acl_user_agent; + private $_cdn_acl_referrer; + + function __construct($api_version) + { + $this->dbug = False; + $this->cabundle_path = NULL; + $this->api_version = $api_version; + $this->error_str = NULL; + + $this->storage_url = NULL; + $this->cdnm_url = NULL; + $this->auth_token = NULL; + + $this->response_status = NULL; + $this->response_reason = NULL; + + # Curl connections array - since there is no way to "re-set" the + # connection paramaters for a cURL handle, we keep an array of + # the unique use-cases and funnel all of those same type + # requests through the appropriate curl connection. + # + $this->connections = array( + "GET_CALL" => NULL, # GET objects/containers/lists + "PUT_OBJ" => NULL, # PUT object + "HEAD" => NULL, # HEAD requests + "PUT_CONT" => NULL, # PUT container + "DEL_POST" => NULL, # DELETE containers/objects, POST objects + "COPY" => null, # COPY objects + ); + + $this->_user_read_progress_callback_func = NULL; + $this->_user_write_progress_callback_func = NULL; + $this->_write_callback_type = NULL; + $this->_text_list = array(); + $this->_return_list = NULL; + $this->_account_container_count = 0; + $this->_account_bytes_used = 0; + $this->_container_object_count = 0; + $this->_container_bytes_used = 0; + $this->_obj_write_resource = NULL; + $this->_obj_write_string = ""; + $this->_obj_etag = NULL; + $this->_obj_last_modified = NULL; + $this->_obj_content_type = NULL; + $this->_obj_content_length = NULL; + $this->_obj_metadata = array(); + $this->_obj_manifest = NULL; + $this->_obj_headers = NULL; + $this->_cdn_enabled = NULL; + $this->_cdn_ssl_uri = NULL; + $this->_cdn_streaming_uri = NULL; + $this->_cdn_uri = NULL; + $this->_cdn_ttl = NULL; + $this->_cdn_log_retention = NULL; + $this->_cdn_acl_user_agent = NULL; + $this->_cdn_acl_referrer = NULL; + + # The OS list with a PHP without an updated CA File for CURL to + # connect to SSL Websites. It is the first 3 letters of the PHP_OS + # variable. + $OS_CAFILE_NONUPDATED=array( + "win","dar" + ); + + if (in_array((strtolower (substr(PHP_OS, 0,3))), $OS_CAFILE_NONUPDATED)) + $this->ssl_use_cabundle(); + + } + + function ssl_use_cabundle($path=NULL) + { + if ($path) { + $this->cabundle_path = $path; + } else { + $this->cabundle_path = dirname(__FILE__) . "/share/cacert.pem"; + } + if (!file_exists($this->cabundle_path)) { + throw new IOException("Could not use CA bundle: " + . $this->cabundle_path); + } + return; + } + + # Uses separate cURL connection to authenticate + # + function authenticate($user, $pass, $acct=NULL, $host=NULL) + { + $path = array(); + if (isset($acct)){ + $headers = array( + sprintf("%s: %s", AUTH_USER_HEADER_LEGACY, $user), + sprintf("%s: %s", AUTH_KEY_HEADER_LEGACY, $pass), + ); + $path[] = $host; + $path[] = rawurlencode(sprintf("v%d",$this->api_version)); + $path[] = rawurlencode($acct); + } else { + $headers = array( + sprintf("%s: %s", AUTH_USER_HEADER, $user), + sprintf("%s: %s", AUTH_KEY_HEADER, $pass), + ); + $path[] = $host; + } + $path[] = "v1.0"; + $url = implode("/", $path); + + $curl_ch = curl_init(); + if (!is_null($this->cabundle_path)) { + curl_setopt($curl_ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($curl_ch, CURLOPT_CAINFO, $this->cabundle_path); + } + curl_setopt($curl_ch, CURLOPT_VERBOSE, $this->dbug); + curl_setopt($curl_ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl_ch, CURLOPT_MAXREDIRS, 4); + curl_setopt($curl_ch, CURLOPT_HEADER, 0); + curl_setopt($curl_ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl_ch, CURLOPT_USERAGENT, USER_AGENT); + curl_setopt($curl_ch, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl_ch, CURLOPT_HEADERFUNCTION,array(&$this,'_auth_hdr_cb')); + curl_setopt($curl_ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl_ch, CURLOPT_URL, $url); + curl_exec($curl_ch); + curl_close($curl_ch); + + return array($this->response_status, $this->response_reason, + $this->storage_url, $this->cdnm_url, $this->auth_token); + } + + # (CDN) GET /v1/Account + # + function list_cdn_containers($enabled_only) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path("CDN"); + + $this->_write_callback_type = "TEXT_LIST"; + if ($enabled_only) + { + $return_code = $this->_send_request($conn_type, $url_path . + '/?enabled_only=true'); + } + else + { + $return_code = $this->_send_request($conn_type, $url_path); + } + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 401) { + return array($return_code,"Unauthorized",array()); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",array()); + } + if ($return_code == 204) { + return array($return_code,"Account has no CDN enabled Containers.", + array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code,$this->response_reason,$this->_text_list); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # (CDN) DELETE /v1/Account/Container or /v1/Account/Container/Object + # + function purge_from_cdn($path, $email=null) + { + if(!$path) + throw new SyntaxException("Path not set"); + $url_path = $this->_make_path("CDN", NULL, $path); + if($email) + { + $hdrs = array(CDN_EMAIL => $email); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"DELETE"); + } + else + $return_code = $this->_send_request("DEL_POST",$url_path,null,"DELETE"); + return $return_code; + } + + # (CDN) POST /v1/Account/Container + function update_cdn_container($container_name, $ttl=86400, $cdn_log_retention=False, + $cdn_acl_user_agent="", $cdn_acl_referrer) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array( + CDN_ENABLED => "True", + CDN_TTL => $ttl, + CDN_LOG_RETENTION => $cdn_log_retention ? "True" : "False", + CDN_ACL_USER_AGENT => $cdn_acl_user_agent, + CDN_ACL_REFERRER => $cdn_acl_referrer, + ); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code, $this->error_str, NULL); + } + if ($return_code == 404) { + $this->error_str = "Container not found."; + return array($return_code, $this->error_str, NULL); + } + if ($return_code != 202) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code, $this->error_str, NULL); + } + return array($return_code, "Accepted", $this->_cdn_uri, $this->_cdn_ssl_uri); + + } + + # (CDN) PUT /v1/Account/Container + # + function add_cdn_container($container_name, $ttl=86400) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array( + CDN_ENABLED => "True", + CDN_TTL => $ttl, + ); + $return_code = $this->_send_request("PUT_CONT", $url_path, $hdrs); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code,$this->response_reason,False); + } + if (!in_array($return_code, array(201,202))) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->response_reason,False); + } + return array($return_code,$this->response_reason,$this->_cdn_uri, + $this->_cdn_ssl_uri); + } + + # (CDN) POST /v1/Account/Container + # + function remove_cdn_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("CDN", $container_name); + $hdrs = array(CDN_ENABLED => "False"); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + if ($return_code == 401) { + $this->error_str = "Unauthorized"; + return array($return_code, $this->error_str); + } + if ($return_code == 404) { + $this->error_str = "Container not found."; + return array($return_code, $this->error_str); + } + if ($return_code != 202) { + $this->error_str="Unexpected HTTP response: ".$this->response_reason; + return array($return_code, $this->error_str); + } + return array($return_code, "Accepted"); + } + + # (CDN) HEAD /v1/Account + # + function head_cdn_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $conn_type = "HEAD"; + $url_path = $this->_make_path("CDN", $container_name); + $return_code = $this->_send_request($conn_type, $url_path, NULL, "GET", True); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 401) { + return array($return_code,"Unauthorized",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); + } + if ($return_code == 204) { + return array($return_code,$this->response_reason, + $this->_cdn_enabled, $this->_cdn_ssl_uri, + $this->_cdn_streaming_uri, + $this->_cdn_uri, $this->_cdn_ttl, + $this->_cdn_log_retention, + $this->_cdn_acl_user_agent, + $this->_cdn_acl_referrer + ); + } + return array($return_code,$this->response_reason, + NULL,NULL,NULL,NULL, + $this->_cdn_log_retention, + $this->_cdn_acl_user_agent, + $this->_cdn_acl_referrer, + NULL + ); + } + + # GET /v1/Account + # + function list_containers($limit=0, $marker=NULL) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path(); + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $this->_write_callback_type = "TEXT_LIST"; + $return_code = $this->_send_request($conn_type, $url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + return array($return_code, "Account has no containers.", array()); + } + if ($return_code == 404) { + $this->error_str = "Invalid account name for authentication token."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code, $this->response_reason, $this->_text_list); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # GET /v1/Account?format=json + # + function list_containers_info($limit=0, $marker=NULL) + { + $conn_type = "GET_CALL"; + $url_path = $this->_make_path() . "?format=json"; + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if (!empty($params)) { + $url_path .= "&" . implode("&", $params); + } + + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type, $url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + return array($return_code, "Account has no containers.", array()); + } + if ($return_code == 404) { + $this->error_str = "Invalid account name for authentication token."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $json_body = json_decode($this->_obj_write_string, True); + return array($return_code, $this->response_reason, $json_body); + } + $this->error_str = "Unexpected HTTP response: ".$this->response_reason; + return array($return_code,$this->error_str,array()); + } + + # HEAD /v1/Account + # + function head_account() + { + $conn_type = "HEAD"; + + $url_path = $this->_make_path(); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,0,0); + } + if ($return_code == 404) { + return array($return_code,"Account not found.",0,0); + } + if ($return_code == 204) { + return array($return_code,$this->response_reason, + $this->_account_container_count, $this->_account_bytes_used); + } + return array($return_code,$this->response_reason,0,0); + } + + # PUT /v1/Account/Container + # + function create_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request("PUT_CONT",$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return False; + } + return $return_code; + } + + # DELETE /v1/Account/Container + # + function delete_container($container_name) + { + if ($container_name == "") + throw new SyntaxException("Container name not set."); + + if ($container_name != "0" and !isset($container_name)) + throw new SyntaxException("Container name not set."); + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request("DEL_POST",$url_path,array(),"DELETE"); + + switch ($return_code) { + case 204: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response.";; + break; + case 409: + $this->error_str = "Container must be empty prior to removing it."; + break; + case 404: + $this->error_str = "Specified container did not exist to delete."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + # GET /v1/Account/Container + # + function list_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) + { + if (!$cname) { + $this->error_str = "Container name not set."; + return array(0, $this->error_str, array()); + } + + $url_path = $this->_make_path("STORAGE", $cname); + + $limit = intval($limit); + $params = array(); + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if ($prefix) { + $params[] = "prefix=".rawurlencode($prefix); + } + if ($path) { + $params[] = "path=".rawurlencode($path); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $conn_type = "GET_CALL"; + $this->_write_callback_type = "TEXT_LIST"; + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 404) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $this->create_array(); + return array($return_code,$this->response_reason, $this->_text_list); + } + $this->error_str = "Unexpected HTTP response code: $return_code"; + return array(0,$this->error_str,array()); + } + + # GET /v1/Account/Container?format=json + # + function get_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL) + { + if (!$cname) { + $this->error_str = "Container name not set."; + return array(0, $this->error_str, array()); + } + + $url_path = $this->_make_path("STORAGE", $cname); + + $limit = intval($limit); + $params = array(); + $params[] = "format=json"; + if ($limit > 0) { + $params[] = "limit=$limit"; + } + if ($marker) { + $params[] = "marker=".rawurlencode($marker); + } + if ($prefix) { + $params[] = "prefix=".rawurlencode($prefix); + } + if ($path) { + $params[] = "path=".rawurlencode($path); + } + if (!empty($params)) { + $url_path .= "?" . implode("&", $params); + } + + $conn_type = "GET_CALL"; + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,array()); + } + if ($return_code == 204) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 404) { + $this->error_str = "Container has no Objects."; + return array($return_code,$this->error_str,array()); + } + if ($return_code == 200) { + $json_body = json_decode($this->_obj_write_string, True); + return array($return_code,$this->response_reason, $json_body); + } + $this->error_str = "Unexpected HTTP response code: $return_code"; + return array(0,$this->error_str,array()); + } + + + # HEAD /v1/Account/Container + # + function head_container($container_name) + { + + if ($container_name == "") { + $this->error_str = "Container name not set."; + return False; + } + + if ($container_name != "0" and !isset($container_name)) { + $this->error_str = "Container name not set."; + return False; + } + + $conn_type = "HEAD"; + + $url_path = $this->_make_path("STORAGE", $container_name); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,0,0); + } + if ($return_code == 404) { + return array($return_code,"Container not found.",0,0); + } + if ($return_code == 204 || $return_code == 200) { + return array($return_code,$this->response_reason, + $this->_container_object_count, $this->_container_bytes_used); + } + return array($return_code,$this->response_reason,0,0); + } + + # GET /v1/Account/Container/Object + # + function get_object_to_string(&$obj, $hdrs=array()) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + $conn_type = "GET_CALL"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $this->_write_callback_type = "OBJECT_STRING"; + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array($return_code0,$this->error_str,NULL); + } + if ($return_code == 404) { + $this->error_str = "Object not found."; + return array($return_code0,$this->error_str,NULL); + } + if (($return_code < 200) || ($return_code > 299 + && $return_code != 412 && $return_code != 304)) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str,NULL); + } + return array($return_code,$this->response_reason, $this->_obj_write_string); + } + + # GET /v1/Account/Container/Object + # + function get_object_to_stream(&$obj, &$resource=NULL, $hdrs=array()) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + if (!is_resource($resource)) { + throw new SyntaxException( + "Resource argument not a valid PHP resource."); + } + + $conn_type = "GET_CALL"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $this->_obj_write_resource = $resource; + $this->_write_callback_type = "OBJECT_STREAM"; + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array($return_code,$this->error_str); + } + if ($return_code == 404) { + $this->error_str = "Object not found."; + return array($return_code,$this->error_str); + } + if (($return_code < 200) || ($return_code > 299 + && $return_code != 412 && $return_code != 304)) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str); + } + return array($return_code,$this->response_reason); + } + + # PUT /v1/Account/Container/Object + # + function put_object(&$obj, &$fp) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + if (!is_resource($fp)) { + throw new SyntaxException( + "File pointer argument is not a valid resource."); + } + + $conn_type = "PUT_OBJ"; + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + + $hdrs = $this->_headers($obj); + + $etag = $obj->getETag(); + if (isset($etag)) { + $hdrs[] = "ETag: " . $etag; + } + if (!$obj->content_type) { + $hdrs[] = "Content-Type: application/octet-stream"; + } else { + $hdrs[] = "Content-Type: " . $obj->content_type; + } + + $this->_init($conn_type); + curl_setopt($this->connections[$conn_type], + CURLOPT_INFILE, $fp); + if (!$obj->content_length) { + # We don''t know the Content-Length, so assumed "chunked" PUT + # + curl_setopt($this->connections[$conn_type], CURLOPT_UPLOAD, True); + $hdrs[] = 'Transfer-Encoding: chunked'; + } else { + # We know the Content-Length, so use regular transfer + # + curl_setopt($this->connections[$conn_type], + CURLOPT_INFILESIZE, $obj->content_length); + } + $return_code = $this->_send_request($conn_type,$url_path,$hdrs); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0,$this->error_str,NULL); + } + if ($return_code == 412) { + $this->error_str = "Missing Content-Type header"; + return array($return_code,$this->error_str,NULL); + } + if ($return_code == 422) { + $this->error_str = "Derived and computed checksums do not match."; + return array($return_code,$this->error_str,NULL); + } + if ($return_code != 201) { + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code,$this->error_str,NULL); + } + return array($return_code,$this->response_reason,$this->_obj_etag); + } + + # POST /v1/Account/Container/Object + # + function update_object(&$obj) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + # TODO: The is_array check isn't in sync with the error message + if (!$obj->manifest && !(is_array($obj->metadata) || is_array($obj->headers))) { + $this->error_str = "Metadata and headers arrays are empty."; + return 0; + } + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + + $hdrs = $this->_headers($obj); + $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST"); + switch ($return_code) { + case 202: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Account, Container, or Object not found."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code"; + break; + } + return $return_code; + } + + # HEAD /v1/Account/Container/Object + # + function head_object(&$obj) + { + if (!is_object($obj) || get_class($obj) != "CF_Object") { + throw new SyntaxException( + "Method argument is not a valid CF_Object."); + } + + $conn_type = "HEAD"; + + $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name); + $return_code = $this->_send_request($conn_type,$url_path); + + if (!$return_code) { + $this->error_str .= ": Failed to obtain valid HTTP response."; + return array(0, $this->error_str." ".$this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + + if ($return_code == 404) { + return array($return_code, $this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + if ($return_code == 204 || $return_code == 200) { + return array($return_code,$this->response_reason, + $this->_obj_etag, + $this->_obj_last_modified, + $this->_obj_content_type, + $this->_obj_content_length, + $this->_obj_metadata, + $this->_obj_manifest, + $this->_obj_headers); + } + $this->error_str = "Unexpected HTTP return code: $return_code"; + return array($return_code, $this->error_str." ".$this->response_reason, + NULL, NULL, NULL, NULL, array(), NULL, array()); + } + + # COPY /v1/Account/Container/Object + # + function copy_object($src_obj_name, $dest_obj_name, $container_name_source, $container_name_target, $metadata=NULL, $headers=NULL) + { + if (!$src_obj_name) { + $this->error_str = "Object name not set."; + return 0; + } + + if ($container_name_source == "") { + $this->error_str = "Container name source not set."; + return 0; + } + + if ($container_name_source != "0" and !isset($container_name_source)) { + $this->error_str = "Container name source not set."; + return 0; + } + + if ($container_name_target == "") { + $this->error_str = "Container name target not set."; + return 0; + } + + if ($container_name_target != "0" and !isset($container_name_target)) { + $this->error_str = "Container name target not set."; + return 0; + } + + $conn_type = "COPY"; + + $url_path = $this->_make_path("STORAGE", $container_name_source, rawurlencode($src_obj_name)); + $destination = rawurlencode($container_name_target."/".$dest_obj_name); + + $hdrs = self::_process_headers($metadata, $headers); + $hdrs[DESTINATION] = $destination; + + $return_code = $this->_send_request($conn_type,$url_path,$hdrs,"COPY"); + switch ($return_code) { + case 201: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Specified container/object did not exist."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + # DELETE /v1/Account/Container/Object + # + function delete_object($container_name, $object_name) + { + if ($container_name == "") { + $this->error_str = "Container name not set."; + return 0; + } + + if ($container_name != "0" and !isset($container_name)) { + $this->error_str = "Container name not set."; + return 0; + } + + if (!$object_name) { + $this->error_str = "Object name not set."; + return 0; + } + + $url_path = $this->_make_path("STORAGE", $container_name,$object_name); + $return_code = $this->_send_request("DEL_POST",$url_path,NULL,"DELETE"); + switch ($return_code) { + case 204: + break; + case 0: + $this->error_str .= ": Failed to obtain valid HTTP response."; + $return_code = 0; + break; + case 404: + $this->error_str = "Specified container did not exist to delete."; + break; + default: + $this->error_str = "Unexpected HTTP return code: $return_code."; + } + return $return_code; + } + + function get_error() + { + return $this->error_str; + } + + function setDebug($bool) + { + $this->dbug = $bool; + foreach ($this->connections as $k => $v) { + if (!is_null($v)) { + curl_setopt($this->connections[$k], CURLOPT_VERBOSE, $this->dbug); + } + } + } + + function getCDNMUrl() + { + return $this->cdnm_url; + } + + function getStorageUrl() + { + return $this->storage_url; + } + + function getAuthToken() + { + return $this->auth_token; + } + + function setCFAuth($cfs_auth, $servicenet=False) + { + if ($servicenet) { + $this->storage_url = "https://snet-" . substr($cfs_auth->storage_url, 8); + } else { + $this->storage_url = $cfs_auth->storage_url; + } + $this->auth_token = $cfs_auth->auth_token; + $this->cdnm_url = $cfs_auth->cdnm_url; + } + + function setReadProgressFunc($func_name) + { + $this->_user_read_progress_callback_func = $func_name; + } + + function setWriteProgressFunc($func_name) + { + $this->_user_write_progress_callback_func = $func_name; + } + + private function _header_cb($ch, $header) + { + $header_len = strlen($header); + + if (preg_match("/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches)) { + $this->response_status = $matches[2]; + $this->response_reason = $matches[3]; + return $header_len; + } + + if (strpos($header, ":") === False) + return $header_len; + list($name, $value) = explode(":", $header, 2); + $value = trim($value); + + switch (strtolower($name)) { + case strtolower(CDN_ENABLED): + $this->_cdn_enabled = strtolower($value) == "true"; + break; + case strtolower(CDN_URI): + $this->_cdn_uri = $value; + break; + case strtolower(CDN_SSL_URI): + $this->_cdn_ssl_uri = $value; + break; + case strtolower(CDN_STREAMING_URI): + $this->_cdn_streaming_uri = $value; + break; + case strtolower(CDN_TTL): + $this->_cdn_ttl = $value; + break; + case strtolower(MANIFEST_HEADER): + $this->_obj_manifest = $value; + break; + case strtolower(CDN_LOG_RETENTION): + $this->_cdn_log_retention = strtolower($value) == "true"; + break; + case strtolower(CDN_ACL_USER_AGENT): + $this->_cdn_acl_user_agent = $value; + break; + case strtolower(CDN_ACL_REFERRER): + $this->_cdn_acl_referrer = $value; + break; + case strtolower(ACCOUNT_CONTAINER_COUNT): + $this->_account_container_count = (float)$value+0; + break; + case strtolower(ACCOUNT_BYTES_USED): + $this->_account_bytes_used = (float)$value+0; + break; + case strtolower(CONTAINER_OBJ_COUNT): + $this->_container_object_count = (float)$value+0; + break; + case strtolower(CONTAINER_BYTES_USED): + $this->_container_bytes_used = (float)$value+0; + break; + case strtolower(ETAG_HEADER): + $this->_obj_etag = $value; + break; + case strtolower(LAST_MODIFIED_HEADER): + $this->_obj_last_modified = $value; + break; + case strtolower(CONTENT_TYPE_HEADER): + $this->_obj_content_type = $value; + break; + case strtolower(CONTENT_LENGTH_HEADER): + $this->_obj_content_length = (float)$value+0; + break; + case strtolower(ORIGIN_HEADER): + $this->_obj_headers[ORIGIN_HEADER] = $value; + break; + default: + if (strncasecmp($name, METADATA_HEADER_PREFIX, strlen(METADATA_HEADER_PREFIX)) == 0) { + $name = substr($name, strlen(METADATA_HEADER_PREFIX)); + $this->_obj_metadata[$name] = $value; + } + elseif ((strncasecmp($name, CONTENT_HEADER_PREFIX, strlen(CONTENT_HEADER_PREFIX)) == 0) || + (strncasecmp($name, ACCESS_CONTROL_HEADER_PREFIX, strlen(ACCESS_CONTROL_HEADER_PREFIX)) == 0)) { + $this->_obj_headers[$name] = $value; + } + } + return $header_len; + } + + private function _read_cb($ch, $fd, $length) + { + $data = fread($fd, $length); + $len = strlen($data); + if (isset($this->_user_write_progress_callback_func)) { + call_user_func($this->_user_write_progress_callback_func, $len); + } + return $data; + } + + private function _write_cb($ch, $data) + { + $dlen = strlen($data); + switch ($this->_write_callback_type) { + case "TEXT_LIST": + $this->_return_list = $this->_return_list . $data; + //= explode("\n",$data); # keep tab,space + //his->_text_list[] = rtrim($data,"\n\r\x0B"); # keep tab,space + break; + case "OBJECT_STREAM": + fwrite($this->_obj_write_resource, $data, $dlen); + break; + case "OBJECT_STRING": + $this->_obj_write_string .= $data; + break; + } + if (isset($this->_user_read_progress_callback_func)) { + call_user_func($this->_user_read_progress_callback_func, $dlen); + } + return $dlen; + } + + private function _auth_hdr_cb($ch, $header) + { + preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches); + if (isset($matches[1])) { + $this->response_status = $matches[1]; + } + if (isset($matches[2])) { + $this->response_reason = $matches[2]; + } + if (stripos($header, STORAGE_URL) === 0) { + $this->storage_url = trim(substr($header, strlen(STORAGE_URL)+1)); + } + if (stripos($header, CDNM_URL) === 0) { + $this->cdnm_url = trim(substr($header, strlen(CDNM_URL)+1)); + } + if (stripos($header, AUTH_TOKEN) === 0) { + $this->auth_token = trim(substr($header, strlen(AUTH_TOKEN)+1)); + } + if (stripos($header, AUTH_TOKEN_LEGACY) === 0) { + $this->auth_token = trim(substr($header,strlen(AUTH_TOKEN_LEGACY)+1)); + } + return strlen($header); + } + + private function _make_headers($hdrs=NULL) + { + $new_headers = array(); + $has_stoken = False; + $has_uagent = False; + if (is_array($hdrs)) { + foreach ($hdrs as $h => $v) { + if (is_int($h)) { + list($h, $v) = explode(":", $v, 2); + } + + if (strncasecmp($h, AUTH_TOKEN, strlen(AUTH_TOKEN)) === 0) { + $has_stoken = True; + } + if (strncasecmp($h, USER_AGENT_HEADER, strlen(USER_AGENT_HEADER)) === 0) { + $has_uagent = True; + } + $new_headers[] = $h . ": " . trim($v); + } + } + if (!$has_stoken) { + $new_headers[] = AUTH_TOKEN . ": " . $this->auth_token; + } + if (!$has_uagent) { + $new_headers[] = USER_AGENT_HEADER . ": " . USER_AGENT; + } + return $new_headers; + } + + private function _init($conn_type, $force_new=False) + { + if (!array_key_exists($conn_type, $this->connections)) { + $this->error_str = "Invalid CURL_XXX connection type"; + return False; + } + + if (is_null($this->connections[$conn_type]) || $force_new) { + $ch = curl_init(); + } else { + return; + } + + if ($this->dbug) { curl_setopt($ch, CURLOPT_VERBOSE, 1); } + + if (!is_null($this->cabundle_path)) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($ch, CURLOPT_CAINFO, $this->cabundle_path); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_MAXREDIRS, 4); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this, '_header_cb')); + + if ($conn_type == "GET_CALL") { + curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$this, '_write_cb')); + } + + if ($conn_type == "PUT_OBJ") { + curl_setopt($ch, CURLOPT_PUT, 1); + curl_setopt($ch, CURLOPT_READFUNCTION, array(&$this, '_read_cb')); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + } + if ($conn_type == "HEAD") { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD"); + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "PUT_CONT") { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_INFILESIZE, 0); + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "DEL_POST") { + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + if ($conn_type == "COPY") { + curl_setopt($ch, CURLOPT_NOBODY, 1); + } + $this->connections[$conn_type] = $ch; + return; + } + + private function _reset_callback_vars() + { + $this->_text_list = array(); + $this->_return_list = NULL; + $this->_account_container_count = 0; + $this->_account_bytes_used = 0; + $this->_container_object_count = 0; + $this->_container_bytes_used = 0; + $this->_obj_etag = NULL; + $this->_obj_last_modified = NULL; + $this->_obj_content_type = NULL; + $this->_obj_content_length = NULL; + $this->_obj_metadata = array(); + $this->_obj_manifest = NULL; + $this->_obj_headers = NULL; + $this->_obj_write_string = ""; + $this->_cdn_streaming_uri = NULL; + $this->_cdn_enabled = NULL; + $this->_cdn_ssl_uri = NULL; + $this->_cdn_uri = NULL; + $this->_cdn_ttl = NULL; + $this->response_status = 0; + $this->response_reason = ""; + } + + private function _make_path($t="STORAGE",$c=NULL,$o=NULL) + { + $path = array(); + switch ($t) { + case "STORAGE": + $path[] = $this->storage_url; break; + case "CDN": + $path[] = $this->cdnm_url; break; + } + if ($c == "0") + $path[] = rawurlencode($c); + + if ($c) { + $path[] = rawurlencode($c); + } + if ($o) { + # mimic Python''s urllib.quote() feature of a "safe" '/' character + # + $path[] = str_replace("%2F","/",rawurlencode($o)); + } + return implode("/",$path); + } + + private function _headers(&$obj) + { + $hdrs = self::_process_headers($obj->metadata, $obj->headers); + if ($obj->manifest) + $hdrs[MANIFEST_HEADER] = $obj->manifest; + + return $hdrs; + } + + private function _process_headers($metadata=null, $headers=null) + { + $rules = array( + array( + 'prefix' => METADATA_HEADER_PREFIX, + ), + array( + 'prefix' => '', + 'filter' => array( # key order is important, first match decides + CONTENT_TYPE_HEADER => false, + CONTENT_LENGTH_HEADER => false, + CONTENT_HEADER_PREFIX => true, + ACCESS_CONTROL_HEADER_PREFIX => true, + ORIGIN_HEADER => true, + ), + ), + ); + + $hdrs = array(); + $argc = func_num_args(); + $argv = func_get_args(); + for ($argi = 0; $argi < $argc; $argi++) { + if(!is_array($argv[$argi])) continue; + + $rule = $rules[$argi]; + foreach ($argv[$argi] as $k => $v) { + $k = trim($k); + $v = trim($v); + if (strpos($k, ":") !== False) throw new SyntaxException( + "Header names cannot contain a ':' character."); + + if (array_key_exists('filter', $rule)) { + $result = null; + foreach ($rule['filter'] as $p => $f) { + if (strncasecmp($k, $p, strlen($p)) == 0) { + $result = $f; + break; + } + } + if (!$result) throw new SyntaxException(sprintf( + "Header name %s is not allowed", $k)); + } + + $k = $rule['prefix'] . $k; + if (strlen($k) > MAX_HEADER_NAME_LEN || strlen($v) > MAX_HEADER_VALUE_LEN) + throw new SyntaxException(sprintf( + "Header %s exceeds maximum length: %d/%d", + $k, strlen($k), strlen($v))); + + $hdrs[$k] = $v; + } + } + + return $hdrs; + } + + private function _send_request($conn_type, $url_path, $hdrs=NULL, $method="GET", $force_new=False) + { + $this->_init($conn_type, $force_new); + $this->_reset_callback_vars(); + $headers = $this->_make_headers($hdrs); + + if (gettype($this->connections[$conn_type]) == "unknown type") + throw new ConnectionNotOpenException ( + "Connection is not open." + ); + + switch ($method) { + case "COPY": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "COPY"); + break; + case "DELETE": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + case "POST": + curl_setopt($this->connections[$conn_type], + CURLOPT_CUSTOMREQUEST, "POST"); + default: + break; + } + + curl_setopt($this->connections[$conn_type], + CURLOPT_HTTPHEADER, $headers); + + curl_setopt($this->connections[$conn_type], + CURLOPT_URL, $url_path); + + if (!curl_exec($this->connections[$conn_type]) && curl_errno($this->connections[$conn_type]) !== 0) { + $this->error_str = "(curl error: " + . curl_errno($this->connections[$conn_type]) . ") "; + $this->error_str .= curl_error($this->connections[$conn_type]); + return False; + } + return curl_getinfo($this->connections[$conn_type], CURLINFO_HTTP_CODE); + } + + function close() + { + foreach ($this->connections as $cnx) { + if (isset($cnx)) { + curl_close($cnx); + $this->connections[$cnx] = NULL; + } + } + } + private function create_array() + { + $this->_text_list = explode("\n",rtrim($this->_return_list,"\n\x0B")); + return True; + } + +} + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php index 95770b44b7..784ed032d4 100644 --- a/apps/files_external/appinfo/app.php +++ b/apps/files_external/appinfo/app.php @@ -9,3 +9,4 @@ OC::$CLASSPATH['OC_Filestorage_FTP']='apps/files_external/lib/ftp.php'; OC::$CLASSPATH['OC_Filestorage_DAV']='apps/files_external/lib/webdav.php'; OC::$CLASSPATH['OC_Filestorage_Google']='apps/files_external/lib/google.php'; +OC::$CLASSPATH['OC_Filestorage_SWIFT']='apps/files_external/lib/swift.php'; diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php new file mode 100644 index 0000000000..ec09742c44 --- /dev/null +++ b/apps/files_external/lib/swift.php @@ -0,0 +1,488 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('php-cloudfiles/cloudfiles.php'); + +class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ + private $host; + private $root; + private $user; + private $token; + private $secure; + /** + * @var CF_Authentication auth + */ + private $auth; + /** + * @var CF_Connection conn + */ + private $conn; + /** + * @var CF_Container rootContainer + */ + private $rootContainer; + + private static $tempFiles=array(); + + const SUBCONTAINER_FILE='.subcontainers'; + + /** + * translate directory path to container name + * @param string path + * @return string + */ + private function getContainerName($path){ + $path=trim($this->root.$path,'/'); + return md5($path); + } + + /** + * get container by path + * @param string path + * @return CF_Container + */ + private function getContainer($path){ + if($path=='' or $path=='/'){ + return $this->rootContainer; + } + try{ + $container=$this->conn->get_container($this->getContainerName($path)); + return $container; + }catch(NoSuchContainerException $e){ + return null; + } + } + + /** + * create container + * @param string path + * @return CF_Container + */ + private function createContainer($path){ + if($path=='' or $path=='/'){ + return $this->conn->create_container($this->getContainerName($path)); + } + $parent=dirname($path); + if($parent=='' or $parent=='/'){ + $parentContainer=$this->rootContainer; + }else{ + if(!$this->containerExists($parent)){ + $parentContainer=$this->createContainer($parent); + }else{ + $parentContainer=$this->getContainer($parent); + } + } + $this->addSubContainer($parentContainer,basename($path)); + return $this->conn->create_container($this->getContainerName($path)); + } + + /** + * get object by path + * @param string path + * @return CF_Object + */ + private function getObject($path){ + $container=$this->getContainer(dirname($path)); + if(is_null($container)){ + return null; + }else{ + try{ + $obj=$container->get_object(basename($path)); + return $obj; + }catch(NoSuchObjectException $e){ + return null; + } + } + } + + /** + * get the names of all objects in a container + * @param CF_Container + * @return array + */ + private function getObjects($container){ + if(is_null($container)){ + return array(); + }else{ + $files=$container->get_objects(); + foreach($files as &$file){ + $file=$file->name; + } + return $files; + } + } + + /** + * create object + * @param string path + * @return CF_Object + */ + private function createObject($path){ + $container=$this->getContainer(dirname($path)); + if(!is_null($container)){ + $container=$this->createContainer($path); + } + return $container->create_object(basename($path)); + } + + /** + * check if an object exists + * @param string + * @return bool + */ + private function objectExists($path){ + return !is_null($this->getObject($path)); + } + + /** + * check if container for path exists + * @param string path + * @return bool + */ + private function containerExists($path){ + return !is_null($this->getContainer($path)); + } + + /** + * get the list of emulated sub containers + * @param CF_Container container + * @return array + */ + private function getSubContainers($container){ + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + }catch(Exception $e){ + return array(); + } + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + unlink($tmpFile); + foreach($containers as &$sub){ + $sub=trim($sub); + } + return $containers; + } + + /** + * add an emulated sub container + * @param CF_Container container + * @param string name + * @return bool + */ + private function addSubContainer($container,$name){ + if(!$name){ + return false; + } + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + foreach($containers as &$sub){ + $sub=trim($sub); + } + if(array_search($name,$containers)!==false){ + unlink($tmpFile); + return false; + }else{ + $fh=fopen($tmpFile,'a'); + fwrite($fh,$name."\n"); + } + }catch(Exception $e){ + $containers=array(); + file_put_contents($tmpFile,$name."\n"); + } + + $obj->load_from_filename($tmpFile); + unlink($tmpFile); + return true; + } + + /** + * remove an emulated sub container + * @param CF_Container container + * @param string name + * @return bool + */ + private function removeSubContainer($container,$name){ + if(!$name){ + return false; + } + $tmpFile=OC_Helper::tmpFile(); + $obj=$this->getSubContainerFile($container); + try{ + $obj->save_to_filename($tmpFile); + $containers=file($tmpFile); + }catch(Exception $e){ + return false; + } + foreach($containers as &$sub){ + $sub=trim($sub); + } + $i=array_search($name,$containers); + if($i===false){ + unlink($tmpFile); + return false; + }else{ + unset($containers[$i]); + file_put_contents($tmpFile,implode("\n",$containers)."\n"); + } + + $obj->load_from_filename($tmpFile); + unlink($tmpFile); + return true; + } + + /** + * ensure a subcontainer file exists and return it's object + * @param CF_Container container + * @return CF_Object + */ + private function getSubContainerFile($container){ + try{ + return $container->get_object(self::SUBCONTAINER_FILE); + }catch(NoSuchObjectException $e){ + return $container->create_object(self::SUBCONTAINER_FILE); + } + } + + public function __construct($params){ + $this->token=$params['token']; + $this->host=$params['host']; + $this->user=$params['user']; + $this->root=isset($params['root'])?$params['root']:'/'; + $this->secure=isset($params['secure'])?(bool)$params['secure']:true; + if(substr($this->root,0,1)!='/'){ + $this->root='/'.$this->root; + } + $this->auth = new CF_Authentication($this->user, $this->token, null, $this->host); + $this->auth->authenticate(); + + $this->conn = new CF_Connection($this->auth); + + if(!$this->containerExists($this->root)){ + $this->rootContainer=$this->createContainer('/'); + }else{ + $this->rootContainer=$this->getContainer('/'); + } + } + + + public function mkdir($path){ + if($this->containerExists($path)){ + return false; + }else{ + $this->createContainer($path); + return true; + } + } + + public function rmdir($path){ + if(!$this->containerExists($path)){ + return false; + }else{ + $this->emptyContainer($path); + if($path!='' and $path!='/'){ + $parentContainer=$this->getContainer(dirname($path)); + $this->removeSubContainer($parentContainer,basename($path)); + } + + $this->conn->delete_container($this->getContainerName($path)); + return true; + } + } + + private function emptyContainer($path){ + $container=$this->getContainer($path); + if(is_null($container)){ + return; + } + $subContainers=$this->getSubContainers($container); + foreach($subContainers as $sub){ + if($sub){ + $this->emptyContainer($path.'/'.$sub); + $this->conn->delete_container($this->getContainerName($path.'/'.$sub)); + } + } + + $objects=$this->getObjects($container); + foreach($objects as $object){ + $container->delete_object($object); + } + } + + public function opendir($path){ + $container=$this->getContainer($path); + $files=$this->getObjects($container); + $i=array_search(self::SUBCONTAINER_FILE,$files); + if($i!==false){ + unset($files[$i]); + } + $subContainers=$this->getSubContainers($container); + $files=array_merge($files,$subContainers); + $id=$this->getContainerName($path); + OC_FakeDirStream::$dirs[$id]=$files; + return opendir('fakedir://'.$id); + } + + public function filetype($path){ + if($this->containerExists($path)){ + return 'dir'; + }else{ + return 'file'; + } + } + + public function is_readable($path){ + return true; + } + + public function is_writable($path){ + return true; + } + + public function file_exists($path){ + if($this->is_dir($path)){ + return true; + }else{ + return $this->objectExists($path); + } + } + + public function file_get_contents($path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + return $obj->read(); + } + + public function file_put_contents($path,$content){ + $obj=$this->getObject($path); + if(is_null($obj)){ + $container=$this->getContainer(dirname($path)); + if(is_null($container)){ + return false; + } + $obj=$container->create_object(basename($path)); + } + return $obj->write($content); + } + + public function unlink($path){ + if($this->objectExists($path)){ + $container=$this->getContainer(dirname($path)); + $container->delete_object(basename($path)); + }else{ + return false; + } + } + + public function fopen($path,$mode){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + switch($mode){ + case 'r': + case 'rb': + $fp = fopen('php://temp', 'r+'); + $obj->stream($fp); + + rewind($fp); + return $fp; + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + $tmpFile=$this->getTmpFile($path); + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + public function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->fromTmpFile($tmpFile,self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + public function free_space($path){ + return 0; + } + + public function touch($path,$mtime=null){ + if(!is_null($mtime)){ + return false; + } + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + $fp = fopen('php://temp', 'r+'); + $obj->stream($fp); + rewind($fp); + $obj->write($fp); + fclose($fp); + } + + public function rename($path1,$path2){ + $sourceContainer=$this->getContainer(dirname($path1)); + $targetContainer=$this->getContainer(dirname($path2)); + return $sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); + } + + public function copy($path1,$path2){ + $sourceContainer=$this->getContainer(dirname($path1)); + $targetContainer=$this->getContainer(dirname($path2)); + return $sourceContainer->copy_object_to(basename($path1),$targetContainer,basename($path2)); + } + + public function stat($path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + return false; + } + return array( + 'mtime'=>strtotime($obj->last_modified), + 'size'=>$obj->content_length, + 'ctime'=>-1, + ); + } + + private function getTmpFile($path){ + $obj=$this->getObject($path); + if(!is_null($obj)){ + $tmpFile=OC_Helper::tmpFile(); + $obj->save_to_filename($tmpFile); + return $tmpFile; + }else{ + return false; + } + } + + private function fromTmpFile($tmpFile,$path){ + $obj=$this->getObject($path); + if(is_null($obj)){ + $obj=$this->createObject($path); + } + $obj->load_from_filename($tmpFile); + } +} diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php index fa4c74a6e2..edbbe9dfec 100644 --- a/apps/files_external/tests/config.php +++ b/apps/files_external/tests/config.php @@ -21,5 +21,12 @@ return array( 'token'=>'test', 'token_secret'=>'test', 'root'=>'/google', - ) + ), + 'swift'=>array( + 'run'=>true, + 'user'=>'test:tester', + 'token'=>'testing', + 'host'=>'localhost:8080/auth', + 'root'=>'/', + ), ); diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php new file mode 100644 index 0000000000..f0bde6ed60 --- /dev/null +++ b/apps/files_external/tests/swift.php @@ -0,0 +1,32 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +$config=include('apps/files_external/tests/config.php'); +if(!is_array($config) or !isset($config['swift']) or !$config['swift']['run']){ + abstract class Test_Filestorage_SWIFT extends Test_FileStorage{} + return; +}else{ + class Test_Filestorage_SWIFT extends Test_FileStorage { + private $config; + private $id; + + public function setUp(){ + $id=uniqid(); + $this->config=include('apps/files_external/tests/config.php'); + $this->config['swift']['root'].='/'.$id;//make sure we have an new empty folder to work in + $this->instance=new OC_Filestorage_SWIFT($this->config['swift']); + } + + + public function tearDown(){ + $this->instance->rmdir(''); + } + + } +} + From 5cefd3466056475f9433d33a74acba0b3ae092e3 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 19 Apr 2012 00:25:47 +0200 Subject: [PATCH 08/12] emulate touch with custom mtime for swift backend --- apps/files_external/lib/swift.php | 50 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/apps/files_external/lib/swift.php b/apps/files_external/lib/swift.php index ec09742c44..e53eb1ff3b 100644 --- a/apps/files_external/lib/swift.php +++ b/apps/files_external/lib/swift.php @@ -373,6 +373,7 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ } $obj=$container->create_object(basename($path)); } + $this->resetMTime($obj); return $obj->write($content); } @@ -429,30 +430,39 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ } public function touch($path,$mtime=null){ - if(!is_null($mtime)){ - return false; - } $obj=$this->getObject($path); if(is_null($obj)){ return false; } - $fp = fopen('php://temp', 'r+'); - $obj->stream($fp); - rewind($fp); - $obj->write($fp); - fclose($fp); + if(is_null($mtime)){ + $mtime=time(); + } + + //emulate setting mtime with metadata + $obj->metadata['Mtime']=$mtime; + $obj->sync_metadata(); } public function rename($path1,$path2){ $sourceContainer=$this->getContainer(dirname($path1)); $targetContainer=$this->getContainer(dirname($path2)); - return $sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); + $result=$sourceContainer->move_object_to(basename($path1),$targetContainer,basename($path2)); + if($result){ + $targetObj=$this->getObject($path2); + $this->resetMTime($targetObj); + } + return $result; } public function copy($path1,$path2){ $sourceContainer=$this->getContainer(dirname($path1)); $targetContainer=$this->getContainer(dirname($path2)); - return $sourceContainer->copy_object_to(basename($path1),$targetContainer,basename($path2)); + $result=$sourceContainer->copy_object_to(basename($path1),$targetContainer,basename($path2)); + if($result){ + $targetObj=$this->getObject($path2); + $this->resetMTime($targetObj); + } + return $result; } public function stat($path){ @@ -460,8 +470,14 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ if(is_null($obj)){ return false; } + + if(isset($obj->metadata['Mtime']) and $obj->metadata['Mtime']>-1){ + $mtime=$obj->metadata['Mtime']; + }else{ + $mtime=strtotime($obj->last_modified); + } return array( - 'mtime'=>strtotime($obj->last_modified), + 'mtime'=>$mtime, 'size'=>$obj->content_length, 'ctime'=>-1, ); @@ -484,5 +500,17 @@ class OC_FileStorage_SWIFT extends OC_Filestorage_Common{ $obj=$this->createObject($path); } $obj->load_from_filename($tmpFile); + $this->resetMTime($obj); + } + + /** + * remove custom mtime metadata + * @param CF_Object obj + */ + private function resetMTime($obj){ + if(isset($obj->metadata['Mtime'])){ + $obj->metadata['Mtime']=-1; + $obj->sync_metadata(); + } } } From 9abb70245aecba79d6b99f5006b06ad78399a77a Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Thu, 19 Apr 2012 16:41:18 +0200 Subject: [PATCH 09/12] showing the file extensions more visible by default. I still think it's shitty design, but I get worse nightmares over thinking that showing file extensions would become a configuration option. --- files/css/files.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/css/files.css b/files/css/files.css index 713adf7b5f..fd551b2762 100644 --- a/files/css/files.css +++ b/files/css/files.css @@ -42,8 +42,8 @@ tbody tr:hover, tbody tr:active, tbody tr.selected { background-color:#f8f8f8; } tbody tr.selected { background-color:#eee; } tbody a { color:#000; } span.extension, td.date { color:#999; } -span.extension { text-transform:lowercase; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } -tr:hover span.extension { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; } +span.extension { text-transform:lowercase; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity:.7; -webkit-transition:opacity 300ms; -moz-transition:opacity 300ms; -o-transition:opacity 300ms; transition:opacity 300ms; } +tr:hover span.extension { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; filter:alpha(opacity=100); opacity:1; color:#777; } div.crumb { float:left; display:block; background:no-repeat right 0; padding:.75em 1.5em 0 1em; height:2.9em; } div.crumb:first-child { padding-left:1em; } div.crumb.last { font-weight:bold; } From 2fbc92bd4bfd69ef7f9c23a620f28daaef83086f Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Fri, 20 Apr 2012 20:49:35 +0200 Subject: [PATCH 10/12] new OC_Mail class to handle all mail sending. The benefit is that is way mor flexible than the standard mail command. can be configured to use a remote smtp relay for example. also port the lostpassword code --- config/config.sample.php | 5 ++ core/lostpassword/index.php | 5 +- lib/mail.php | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 lib/mail.php diff --git a/config/config.sample.php b/config/config.sample.php index 199c9248c5..9f6d674fc0 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -19,6 +19,11 @@ $CONFIG = array( "knowledgebaseurl" => "", "appstoreenabled" => true, "appstoreurl" => "", +"mail_smtpmode" => "sendmail", +"mail_smtphost" => "127.0.0.1", +"mail_smtpauth" => "false", +"mail_smtpname" => "", +"mail_smtppassword" => "", // "datadirectory" => "" ); ?> diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index 89bb6cfa79..b6cdd601d6 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -22,7 +22,10 @@ if (isset($_POST['user'])) { $msg = $tmpl->fetchPage(); $l = OC_L10N::get('core'); $from = 'lostpassword-noreply@' . $_SERVER['HTTP_HOST']; - mail($email, $l->t('Owncloud password reset'), $msg, 'From:' . $from); + $r=mail($email, $l->t('Owncloud password reset'), $msg, 'From:' . $from); +//if($r==false) echo('error'); else echo('works!!!!!!!'); + OC_MAIL::send($email,$_POST['user'],$l->t('Owncloud password reset'),$msg,$from,'ownCloud'); + } OC_Template::printGuestPage('core/lostpassword', 'lostpassword', array('error' => false, 'requested' => true)); } else { diff --git a/lib/mail.php b/lib/mail.php new file mode 100644 index 0000000000..0045f8de6d --- /dev/null +++ b/lib/mail.php @@ -0,0 +1,116 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * OC_Mail + * + * A class to handle mail sending. + */ + +require_once('class.phpmailer.php'); + +class OC_Mail { + + /** + * send an email + * + * @param string $toaddress + * @param string $toname + * @param string $subject + * @param string $mailtext + * @param string $fromaddress + * @param string $fromname + * @param bool $html + */ + public static function send($toaddress,$toname,$subject,$mailtext,$fromaddress,$fromname,$html=0,$altbody='',$ccaddress='',$ccname='',$bcc='') { + + $SMTPMODE = OC_Config::getValue( 'mail_smtpmode', 'sendmail' ); + $SMTPHOST = OC_Config::getValue( 'mail_smtphost', '127.0.0.1' ); + $SMTPAUTH = OC_Config::getValue( 'mail_smtpauth', 'false' ); + $SMTPUSERNAME = OC_Config::getValue( 'mail_smtpname', '' ); + $SMTPPASSWORD = OC_Config::getValue( 'mail_smtppassword', '' ); + + + $mailo = new PHPMailer(); + if($SMTPMODE=='sendmail') { + $mailo->IsSendmail(); + }elseif($SMTPMODE=='smtp'){ + $mailo->IsSMTP(); + }elseif($SMTPMODE=='qmail'){ + $mailo->IsQmail(); + }else{ + $mailo->IsMail(); + } + + + $mailo->Host = $SMTPHOST; + $mailo->SMTPAuth = $SMTPAUTH; + $mailo->Username = $SMTPUSERNAME; + $mailo->Password = $SMTPPASSWORD; + + $mailo->From =$fromaddress; + $mailo->FromName = $fromname;; + $a=explode(' ',$toaddress); + foreach($a as $ad) { + $mailo->AddAddress($ad,$toname); + } + + if($ccaddress<>'') $mailo->AddCC($ccaddress,$ccname); + if($bcc<>'') $mailo->AddBCC($bcc); + + $mailo->AddReplyTo($fromaddress, $fromname); + + $mailo->WordWrap = 50; + if($html==1) $mailo->IsHTML(true); else $mailo->IsHTML(false); + + $mailo->Subject = $subject; + if($altbody=='') { + $mailo->Body = $mailtext.OC_MAIL::getfooter(); + $mailo->AltBody = ''; + }else{ + $mailo->Body = $mailtext; + $mailo->AltBody = $altbody; + } + $mailo->CharSet = 'UTF-8'; + + $mailo->Send(); + unset($mailo); + + OC_Log::write('Mail from '.$fromname.' ('.$fromaddress.')'.' to: '.$toname.'('.$toaddress.')'.' subject: '.$subject,'mail',OC_Log::DEBUG); + + } + + + + /** + * sending a mail based on a template + * + * @param texttemplate $texttemplate + * @param htmltemplate $htmltemplate + * @param data $data + * @param To $toaddress + * @param ToName $toname + * @param Subject $subject + * @param From $fromaddress + * @param FromName $fromname + * @param ccaddress $ccaddress + * @param ccname $ccname + * @param bcc $bcc + */ + public static function getfooter() { + + $txt="\n--\n"; + $txt.="ownCloud\n"; + $txt.="Your Cloud, Your Data, Your Way!\n"; + return($txt); + + } + + + +} From c3cb000cb23ef9e17f0443b16f2291a0a885126c Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Fri, 20 Apr 2012 20:53:36 +0200 Subject: [PATCH 11/12] add phpmailer to 3rdparty --- 3rdparty/class.phpmailer.php | 2473 ++++++++++++++++++++++++++++++++++ 3rdparty/class.smtp.php | 817 +++++++++++ 2 files changed, 3290 insertions(+) create mode 100644 3rdparty/class.phpmailer.php create mode 100644 3rdparty/class.smtp.php diff --git a/3rdparty/class.phpmailer.php b/3rdparty/class.phpmailer.php new file mode 100644 index 0000000000..4589cd791e --- /dev/null +++ b/3rdparty/class.phpmailer.php @@ -0,0 +1,2473 @@ +exceptions = ($exceptions == true); + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('ReplyTo', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|ReplyTo)$/', $kind)) { + $this->SetError($this->Lang('Invalid recipient array').': '.$kind); + if ($this->exceptions) { + throw new phpmailerException('Invalid recipient array: ' . $kind); + } + echo $this->Lang('Invalid recipient array').': '.$kind; + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + if ($kind != 'ReplyTo') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + +/** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @return boolean + */ + public function SetFrom($address, $name = '', $auto = 1) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!self::ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + echo $this->Lang('invalid_address').': '.$address; + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->ReplyTo)) { + $this->AddAnAddress('ReplyTo', $address, $name); + } + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation + * Tries to use PHP built-in validator in the filter extension (from PHP 5.2), falls back to a reasonably competent regex validator + * Conforms approximately to RFC2822 + * @link http://www.hexillion.com/samples/#Regex Original pattern found here + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (function_exists('filter_var')) { //Introduced in PHP 5.2 + if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) { + return false; + } else { + return true; + } + } else { + return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $address); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @return bool + */ + public function Send() { + try { + if(!$this->PreSend()) return false; + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + protected function PreSend() { + try { + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + //Refuse to send an empty message + if (empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + $this->MIMEHeader = $this->CreateHeader(); + $this->MIMEBody = $this->CreateBody(); + + + // digitally sign with DKIM if enabled + if ($this->DKIM_domain && $this->DKIM_private) { + $header_dkim = $this->DKIM_Add($this->MIMEHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader; + } + + return true; + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + protected function PostSend() { + try { + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); + default: + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + } + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + return false; + } + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f %s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $key => $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = "-oi -f %s"; + } else { + $params = sprintf("-oi -f %s", $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $key => $val) { + $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + throw new phpmailerException($this->Lang('from_failed') . $smtp_from, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @uses SMTP + * @access public + * @return bool + */ + public function SmtpConnect() { + if(is_null($this->smtp)) { + $this->smtp = new SMTP(); + } + + $this->smtp->do_debug = $this->SMTPDebug; + $hosts = explode(';', $this->Host); + $index = 0; + $connection = $this->smtp->Connected(); + + // Retry while there is no connection + try { + while($index < count($hosts) && !$connection) { + $hostinfo = array(); + if (preg_match('/^(.+):([0-9]+)$/', $hosts[$index], $hostinfo)) { + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } else { + $host = $hosts[$index]; + $port = $this->Port; + } + + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout)) { + + $hello = ($this->Helo != '' ? $this->Helo : $this->ServerHostname()); + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('tls')); + } + + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + + $connection = true; + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + } + $index++; + if (!$connection) { + throw new phpmailerException($this->Lang('connect_host')); + } + } + } catch (phpmailerException $e) { + $this->smtp->Reset(); + throw $e; + } + return true; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if(!is_null($this->smtp)) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'provide_address' => 'You must provide at least one recipient email address.', + 'mailer_not_supported' => ' mailer is not supported.', + 'execute' => 'Could not execute: ', + 'instantiate' => 'Could not instantiate mail function.', + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'from_failed' => 'The following From address failed: ', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'encoding' => 'Unknown encoding: ', + 'signing' => 'Signing Error: ', + 'smtp_error' => 'SMTP server error: ', + 'empty_message' => 'Message body empty', + 'invalid_address' => 'Invalid address', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + + $message = $this->FixEOL($message); + if (substr($message, -1) == $this->LE) { + $message = substr($message, 0, -1); + } + + $line = explode($this->LE, $message); + $message = ''; + for ($i = 0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", $this->LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . $this->LE; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + $this->boundary[3] = 'b3_' . $uniq_id; + + $result .= $this->HeaderLine('Date', self::RFCDate()); + if($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', trim($this->From)); + } else { + $result .= $this->HeaderLine('Return-Path', trim($this->Sender)); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-to', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID', $this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + if($this->XMailer) { + $result .= $this->HeaderLine('X-Mailer', $this->XMailer); + } else { + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (http://code.google.com/a/apache-extras.org/p/phpmailer/)'); + } + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'plain': + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset="'.$this->CharSet.'"'); + break; + case 'inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"'); + break; + } + + if($this->Mailer != 'mail') { + $result .= $this->LE.$this->LE; + } + + return $result; + } + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME(); + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'plain': + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + case 'inline': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[1]); + break; + case 'attach': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'inline_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[2]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $this->TextLine("--" . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine("--" . $this->boundary[2]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3] . '"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll("inline", $this->boundary[3]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll("attachment", $this->boundary[1]); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + $file = tempnam('', 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam("", "signed"); + if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), NULL)) { + @unlink($file); + @unlink($signed); + $body = file_get_contents($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang("signing").openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + + return $body; + } + + /** + * Returns the start of a message boundary. + * @access protected + * @return string + */ + protected function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset=\"%s\"", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access protected + * @return string + */ + protected function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access protected + * @return void + */ + protected function SetMessageType() { + $this->message_type = array(); + if($this->AlternativeExists()) $this->message_type[] = "alt"; + if($this->InlineImageExists()) $this->message_type[] = "inline"; + if($this->AttachmentExists()) $this->message_type[] = "attach"; + $this->message_type = implode("_", $this->message_type); + if($this->message_type == "") $this->message_type = "plain"; + } + + /** + * Returns a formatted header line. + * @access public + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + echo $e->getMessage()."\n"; + if ( $e->getCode() == self::STOP_CRITICAL ) { + return false; + } + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @return string + */ + protected function AttachAll($disposition_type, $boundary) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // CHECK IF IT IS A VALID DISPOSITION_FILTER + if($attachment[6] == $disposition_type) { + // Check for string attachment + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { continue; } + $incl[] = $inclhash; + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $boundary, $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + } + + $mime[] = sprintf("--%s--%s", $boundary, $this->LE); + + return implode("", $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @see EncodeFile() + * @access protected + * @return string + */ + protected function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + if (function_exists('get_magic_quotes')) { + function get_magic_quotes() { + return false; + } + } + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + $magic_quotes = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if (strlen($str)/3 < $x) { + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.$this->LE, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @return string + */ + public function Base64EncodeWrapMB($str) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $this->LE; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($this->LE)); + return $encoded; + } + + /** + * Encode string to quoted-printable. + * Only uses standard PHP, slow, but will always work + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + */ + public function EncodeQPphp( $input = '', $line_max = 76, $space_conv = false) { + $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'); + $lines = preg_split('/(?:\r\n|\r|\n)/', $input); + $eol = "\r\n"; + $escape = '='; + $output = ''; + while( list(, $line) = each($lines) ) { + $linlen = strlen($line); + $newline = ''; + for($i = 0; $i < $linlen; $i++) { + $c = substr( $line, $i, 1 ); + $dec = ord( $c ); + if ( ( $i == 0 ) && ( $dec == 46 ) ) { // convert first point in the line into =2E + $c = '=2E'; + } + if ( $dec == 32 ) { + if ( $i == ( $linlen - 1 ) ) { // convert space at eol only + $c = '=20'; + } else if ( $space_conv ) { + $c = '=20'; + } + } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required + $h2 = floor($dec/16); + $h1 = floor($dec%16); + $c = $escape.$hex[$h2].$hex[$h1]; + } + if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted + $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay + $newline = ''; + // check if newline first character will be point or not + if ( $dec == 46 ) { + $c = '=2E'; + } + } + $newline .= $c; + } // end of for + $output .= $newline.$eol; + } // end of while + return $output; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * Uses a PHP5 stream filter to do the encoding about 64x faster than the old version + * Also results in same content as you started with after decoding + * @see EncodeQPphp() + * @access public + * @param string $string the text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @param boolean $space_conv Dummy param for compatibility with existing EncodeQP function + * @return string + * @author Marcus Bointon + */ + public function EncodeQP($string, $line_max = 76, $space_conv = false) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + $filters = stream_get_filters(); + if (!in_array('convert.*', $filters)) { //Got convert stream filter? + return $this->EncodeQPphp($string, $line_max, $space_conv); //Fall back to old implementation + } + $fp = fopen('php://temp/', 'r+'); + $string = preg_replace('/\r\n?/', $this->LE, $string); //Normalise line breaks + $params = array('line-length' => $line_max, 'line-break-chars' => $this->LE); + $s = stream_filter_append($fp, 'convert.quoted-printable-encode', STREAM_FILTER_READ, $params); + fputs($fp, $string); + rewind($fp); + $out = stream_get_contents($fp); + stream_filter_remove($s); + $out = preg_replace('/^\./m', '=2E', $out); //Encode . if it is first char on a line, workaround for bug in Exchange + fclose($fp); + return $out; + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ($str, $position = 'text') { + // There should not be any EOL in the string + $encoded = preg_replace('/[\r\n]*/', '', $str); + + switch (strtolower($position)) { + case 'phrase': + $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + break; + case 'comment': + $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded); + case 'text': + default: + // Replace every high ascii, control =, ? and _ characters + //TODO using /e (equivalent to eval()) is probably not a good idea + $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e', + "'='.sprintf('%02X', ord(stripslashes('\\1')))", $encoded); + break; + } + + // Replace every spaces to _ (more readable than =20) + $encoded = str_replace(' ', '_', $encoded); + + return $encoded; + } + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Adds an embedded attachment. This can include images, sounds, and + * just about any other document. Make sure to set the $type to an + * image type. For JPEG images use "image/jpeg" and for GIF images + * use "image/gif". + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment. Use this to identify + * the Id for accessing the image in an HTML form. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') { + + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + + return true; + } + + public function AddStringEmbeddedImage($string, $cid, $filename = '', $encoding = 'base64', $type = 'application/octet-stream') { + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + public function AttachmentExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + public function AlternativeExists() { + return strlen($this->AltBody)>0; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

\n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + $tz = date('Z'); + $tzs = ($tz < 0) ? '-' : '+'; + $tz = abs($tz); + $tz = (int)($tz/3600)*100 + ($tz%3600)/60; + $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz); + + return $result; + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access protected + * @return string + */ + protected function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CR or LF to CRLF. + * @access public + * @return string + */ + public function FixEOL($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + $str = str_replace("\n", $this->LE, $str); + return $str; + } + + /** + * Adds a custom header. + * @access public + * @return void + */ + public function AddCustomHeader($custom_header) { + $this->CustomHeader[] = explode(':', $custom_header, 2); + } + + /** + * Evaluates the message and returns modifications for inline images and backgrounds + * @access public + * @return $message + */ + public function MsgHTML($message, $basedir = '') { + preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images); + if(isset($images[2])) { + foreach($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#', $url)) { + $filename = basename($url); + $directory = dirname($url); + ($directory == '.') ? $directory='': ''; + $cid = 'cid:' . md5($filename); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $mimeType = self::_mime_types($ext); + if ( strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; } + if ( strlen($directory) > 1 && substr($directory, -1) != '/') { $directory .= '/'; } + if ( $this->AddEmbeddedImage($basedir.$directory.$filename, md5($filename), $filename, 'base64', $mimeType) ) { + $message = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + $this->Body = $message; + $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/s', '', $message))); + if (!empty($textMsg) && empty($this->AltBody)) { + $this->AltBody = html_entity_decode($textMsg); + } + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'doc' => 'application/msword', + 'bin' => 'application/macbinary', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name Parameter Name + * @param mixed $value Parameter Value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str String + * @return string + */ + public function SecureHeader($str) { + $str = str_replace("\r", '', $str); + $str = str_replace("\n", '', $str); + return trim($str); + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $key_filename Parameter File Name + * @param string $key_pass Password for private key + */ + public function DKIM_QP($txt) { + $tmp = ''; + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) { + $line .= $txt[$i]; + } else { + $line .= "=".sprintf("%02X", $ord); + } + } + return $line; + } + + /** + * Generate DKIM signature + * + * @access public + * @param string $s Header + */ + public function DKIM_Sign($s) { + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + */ + public function DKIM_HeaderC($s) { + $s = preg_replace("/\r\n\s+/", " ", $s); + $lines = explode("\r\n", $s); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(":", $line, 2); + $heading = strtolower($heading); + $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces + $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s = implode("\r\n", $lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + */ + public function DKIM_Add($headers_line, $subject, $body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + foreach($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return "X-PHPMAILER-DKIM: phpmailer.worxware.com\r\n".$dkimhdrs.$signed."\r\n"; + } + + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body) { + if (!empty($this->action_function) && function_exists($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body); + call_user_func_array($this->action_function, $params); + } + } +} + +class phpmailerException extends Exception { + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} +?> diff --git a/3rdparty/class.smtp.php b/3rdparty/class.smtp.php new file mode 100644 index 0000000000..07c275936c --- /dev/null +++ b/3rdparty/class.smtp.php @@ -0,0 +1,817 @@ +smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to the server specified on the port specified. + * If the port is not specified use the default SMTP_PORT. + * If tval is specified then a connection will try and be + * established with the server for that number of seconds. + * If tval is not specified the default is 30 seconds to + * try on the connection. + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @return bool + */ + public function Connect($host, $port = 0, $tval = 30) { + // set the error val to null so there is no confusion + $this->error = null; + + // make sure we are __not__ connected + if($this->connected()) { + // already connected, generate error + $this->error = array("error" => "Already connected to a server"); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // connect to the smtp server + $this->smtp_conn = @fsockopen($host, // the host of the server + $port, // the port to use + $errno, // error number if any + $errstr, // error message if any + $tval); // give up after ? secs + // verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array("error" => "Failed to connect to server", + "errno" => $errno, + "errstr" => $errstr); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": $errstr ($errno)" . $this->CRLF . '
'; + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != "WIN") + socket_set_timeout($this->smtp_conn, $tval, 0); + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $announce . $this->CRLF . '
'; + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array("error" => "Called StartTLS() without being connected"); + return false; + } + + fputs($this->smtp_conn,"STARTTLS" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 220) { + $this->error = + array("error" => "STARTTLS not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @return bool + */ + public function Authenticate($username, $password) { + // Start authentication + fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "AUTH not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded username + fputs($this->smtp_conn, base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 334) { + $this->error = + array("error" => "Username not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + // Send encoded password + fputs($this->smtp_conn, base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($code != 235) { + $this->error = + array("error" => "Password not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = socket_get_status($this->smtp_conn); + if($sock_status["eof"]) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE:" . $this->CRLF . "EOF caught while checking if connected"; + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being seperated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552,554,451,452 + * SMTP CODE FAILURE: 451,554 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Data() without being connected"); + return false; + } + + fputs($this->smtp_conn,"DATA" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 354) { + $this->error = + array("error" => "DATA command not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n","\n",$msg_data); + $msg_data = str_replace("\r","\n",$msg_data); + $lines = explode("\n",$msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0],0,strpos($lines[0],":")); + $in_headers = false; + if(!empty($field) && !strstr($field," ")) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(,$line) = @each($lines)) { + $lines_out = null; + if($line == "" && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line,0,$max_line_length)," "); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos); + } else { + $lines_out[] = substr($line,0,$pos); + $line = substr($line,$pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(,$line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == ".") { + $line_out = "." . $line_out; + } + } + fputs($this->smtp_conn,$line_out . $this->CRLF); + } + } + + // message data has been sent + fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "DATA not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Hello() without being connected"); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = "localhost"; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello("EHLO", $host)) { + if(!$this->SendHello("HELO", $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access private + * @return bool + */ + private function SendHello($hello, $host) { + fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER: " . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => $hello . " not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,421 + * @access public + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Mail() without being connected"); + return false; + } + + $useVerp = ($this->do_verp ? "XVERP" : ""); + fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "MAIL not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Quit() without being connected"); + return false; + } + + // send the quit command to the server + fputs($this->smtp_conn,"quit" . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $byemsg . $this->CRLF . '
'; + } + + $rval = true; + $e = null; + + $code = substr($byemsg,0,3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array("error" => "SMTP server rejected quit command", + "smtp_code" => $code, + "smtp_rply" => substr($byemsg,4)); + $rval = false; + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $e["error"] . ": " . $byemsg . $this->CRLF . '
'; + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250,251 + * SMTP CODE FAILURE: 550,551,552,553,450,451,452 + * SMTP CODE ERROR : 500,501,503,421 + * @access public + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Recipient() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250 && $code != 251) { + $this->error = + array("error" => "RCPT not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500,501,504,421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called Reset() without being connected"); + return false; + } + + fputs($this->smtp_conn,"RSET" . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "RSET failed", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552,451,452 + * SMTP CODE SUCCESS: 500,501,502,421 + * @access public + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + "error" => "Called SendAndMail() without being connected"); + return false; + } + + fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply,0,3); + + if($this->do_debug >= 2) { + echo "SMTP -> FROM SERVER:" . $rply . $this->CRLF . '
'; + } + + if($code != 250) { + $this->error = + array("error" => "SAML not accepted from server", + "smtp_code" => $code, + "smtp_msg" => substr($rply,4)); + if($this->do_debug >= 1) { + echo "SMTP -> ERROR: " . $this->error["error"] . ": " . $rply . $this->CRLF . '
'; + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array("error" => "This method, TURN, of the SMTP ". + "is not implemented"); + if($this->do_debug >= 1) { + echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF . '
'; + } + return false; + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access private + * @return string + */ + private function get_lines() { + $data = ""; + while($str = @fgets($this->smtp_conn,515)) { + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data was \"$data\"" . $this->CRLF . '
'; + echo "SMTP -> get_lines(): \$str is \"$str\"" . $this->CRLF . '
'; + } + $data .= $str; + if($this->do_debug >= 4) { + echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF . '
'; + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str,3,1) == " ") { break; } + } + return $data; + } + +} + +?> From 0f5864d864f63a7142064d142661c46644b2d0db Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Fri, 20 Apr 2012 21:49:57 +0200 Subject: [PATCH 12/12] support searching the knowledge base --- lib/ocsclient.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ocsclient.php b/lib/ocsclient.php index d830a4f3e7..9ad9ef21c6 100755 --- a/lib/ocsclient.php +++ b/lib/ocsclient.php @@ -199,7 +199,7 @@ class OC_OCSClient{ * * This function returns a list of all the knowledgebase entries from the OCS server */ - public static function getKnownledgebaseEntries($page,$pagesize){ + public static function getKnownledgebaseEntries($page,$pagesize,$search=''){ if(OC_Config::getValue('knowledgebaseenabled', true)==false){ $kbe=array(); $kbe['totalitems']=0; @@ -208,7 +208,8 @@ class OC_OCSClient{ $p= (int) $page; $s= (int) $pagesize; - $url=OC_OCSClient::getKBURL().'/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s; + if($search<>'') $searchcmd='&search='.urlencode($search); else $searchcmd=''; + $url=OC_OCSClient::getKBURL().'/knowledgebase/data?type=150&page='.$p.'&pagesize='.$s.$searchcmd; $kbe=array(); $xml=@file_get_contents($url);