12.02.2017, 19:58
Hallo @all,
the common way to create self-executing php scripts is to use Phar.
Auf manchen shared-hosts oder älteren compilationen steht dieses nicht zur Verfügung.
Deshalb möchte ich meine Alternative präsentieren, sie basiert auf dem MIME 1.0 protokoll bzw. dem multipart/mixed Content-Type.
Das Script ist auch als gist verfügbar und befindet sich auch im Anhang dieses Postings.
Das frdl/webfan package dient als Live Example Beispiel: http://www.webfan.de/install/
Anmerkung: Wer sich für den "common way" mit phar entscheidet, für den ist vielleicht dieses package interessant: https://packagist.org/packages/phine/phar
Wie das .phar Format auch, macht das script Gebrauch von der Anweisung
Wenn dieser Token in einer Datei vorkommt, wird alles nachfolgende vom php-compiler ignoriert.
Dies erlaubt uns an eine .php Datei beliebiege Daten anzuhägen ohne einen parse error zu verursachen.
Die php Constant
hilft uns dabei die Daten, bzw. die Position der angehängten Daten in der php Datei zu finden.
Unser script speichert die Daten im Anhang als MIME mutlipart/mixed Format.
Die Dateien im ersten multipart mit dem Content-Type
werden entsprechend als php ausgeführt.
In der Vorlage-Datei bootstrap.php befindet sich die Passage
Dies installiert einen Autolader welcher Klassen nach einem vorgegebenen Schema direkt aus der Mime Datei lädt.
Siehe script und method Autolad
.
Create the script
Um ein self-executing script zu erstellen lade eine Instanz der MIME-VM z.B. per autoloader oder vorherigem include in eine Variable z.B. $vm:
Get a file within $vm
Clear its contents
Clear its contents and then append something
Wenn man die $vm fertig bearbeitet hat kann man das resultierende self-executing php script ganz einfach abspeichern mit
@ToDo: Der MIME-Parser ist derzeit nicht memory optimiert und es kann bei größeren Anhängen das memory limit überschritten werden. Es sollten zunächst nur die Datei-Header eingelesen werden um dies zu vermeiden.
the common way to create self-executing php scripts is to use Phar.
Auf manchen shared-hosts oder älteren compilationen steht dieses nicht zur Verfügung.
Deshalb möchte ich meine Alternative präsentieren, sie basiert auf dem MIME 1.0 protokoll bzw. dem multipart/mixed Content-Type.
Das Script ist auch als gist verfügbar und befindet sich auch im Anhang dieses Postings.
Das frdl/webfan package dient als Live Example Beispiel: http://www.webfan.de/install/
Anmerkung: Wer sich für den "common way" mit phar entscheidet, für den ist vielleicht dieses package interessant: https://packagist.org/packages/phine/phar
Wie das .phar Format auch, macht das script Gebrauch von der Anweisung
PHP-Code:
__halt_compiler
Wenn dieser Token in einer Datei vorkommt, wird alles nachfolgende vom php-compiler ignoriert.
Dies erlaubt uns an eine .php Datei beliebiege Daten anzuhägen ohne einen parse error zu verursachen.
Die php Constant
PHP-Code:
__COMPILER_HALT_OFFSET__
Unser script speichert die Daten im Anhang als MIME mutlipart/mixed Format.
Die Dateien im ersten multipart mit dem Content-Type
PHP-Code:
'application/vnd.frdl.script.php' => '_run_php_1',
'application/php' => '_run_php_1',
'text/php' => '_run_php_1',
'php' => '_run_php_1',
'multipart/mixed' => '_run_multipart',
'multipart/serial' => '_run_multipart',
'multipart/related' => '_run_multipart',
'application/x-httpd-php' => '_run_php_1',
werden entsprechend als php ausgeführt.
In der Vorlage-Datei bootstrap.php befindet sich die Passage
PHP-Code:
spl_autoload_register(array($this,'Autoload'), true, true);
Dies installiert einen Autolader welcher Klassen nach einem vorgegebenen Schema direkt aus der Mime Datei lädt.
Siehe script und method Autolad

Create the script
Um ein self-executing script zu erstellen lade eine Instanz der MIME-VM z.B. per autoloader oder vorherigem include in eine Variable z.B. $vm:
PHP-Code:
$vm = \webfan\MimeStubAPC::vm();
Get a file within $vm
PHP-Code:
$vm->get_file($vm->document, '$HOME/index.php', 'stub index.php')
Clear its contents
PHP-Code:
$vm->get_file($vm->document, '$HOME/index.php', 'stub index.php')
->clear()
Clear its contents and then append something
PHP-Code:
$vm->get_file($vm->document, '$HOME/index.php', 'stub index.php')
->clear()
->append(file_get_contents($SDIR.'stub'.DIRECTORY_SEPARATOR.'HOME'. DIRECTORY_SEPARATOR .'index.php' ))
;
Wenn man die $vm fertig bearbeitet hat kann man das resultierende self-executing php script ganz einfach abspeichern mit
PHP-Code:
$vm->location = __DIR__. DIRECTORY_SEPARATOR. 'newscript.php';
@ToDo: Der MIME-Parser ist derzeit nicht memory optimiert und es kann bei größeren Anhängen das memory limit überschritten werden. Es sollten zunächst nur die Datei-Header eingelesen werden um dies zu vermeiden.
PHP-Code:
<?php
/**
* Copyright (c) 2017, Till Wehowski
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of frdl/webfan nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY frdl/webfan ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL frdl/webfan BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
*/
namespace webfan;
use frdl;
define('___BLOCK_WEBFAN_MIME_VM_RUNNING_STUB___', true);
$run = function($file = null){
$args = func_get_args();
header_remove();
$MimeVM = new MimeVM($args[0]);
$MimeVM('run');
return $MimeVM;
};
$included_files = get_included_files();
if(!___BLOCK_WEBFAN_MIME_VM_RUNNING_STUB___ && !in_array(__FILE__, $included_files) || __FILE__===$included_files[0]) {
$run(__FILE__);
}
class Context
{
}
class Env
{
}
class Response
{
}
class MimeVM
{
public $e_level = E_USER_ERROR;
protected $Request = false;
protected $Response = false;
protected $raw = false;
protected $MIME = false;
protected $__FILE__ = false;
protected $buf;
//stream
protected $IO = false;
protected $file = false;
protected $host = false;
protected $mode = false;
protected $offset = false;
protected $Context = false;
protected $Env = false;
protected $initial_offset = 0;
protected $php = array();
protected $mimes_engine = array(
'application/vnd.frdl.script.php' => '_run_php_1',
'application/php' => '_run_php_1',
'text/php' => '_run_php_1',
'php' => '_run_php_1',
'multipart/mixed' => '_run_multipart',
'multipart/serial' => '_run_multipart',
'multipart/related' => '_run_multipart',
'application/x-httpd-php' => '_run_php_1',
);
protected function _run_multipart($_Part){
foreach( $_Part->getParts() as $pos => $part){
if(isset($this->mimes_engine[$part->getMimeType()])){
call_user_func_array(array($this, $this->mimes_engine[$part->getMimeType()]), array($part));
}
}
}
protected function runStubs(){
//echo $this->document;
// return;
foreach( $this->document->getParts() as $rootPos => $rootPart){
if($rootPart->isMultiPart()) {
foreach( $rootPart->getParts() as $pos => $part){
if(isset($this->mimes_engine[$part->getMimeType()])){
call_user_func_array(array($this, $this->mimes_engine[$part->getMimeType()]), array($part));
}
}
}
break;
}
}
public function get_file($part, $file, $name){
if($part->isMultiPart()) {
foreach( $part->getParts() as $pos => $_part){
$_f = $this->get_file($_part, $file, $name);
if(false !== $_f)return $_f;
}
} else{
if($file === $part->getFileName() || $name === $part->getName()){
$_f = &$part;
return $_f;
}
}
return false;
}
public function Autoload($class){
$fnames = array(
'$LIB/'.str_replace('\\', '/', $class).'.php',
str_replace('\\', '/', $class).'.php',
'$DIR_PSR4/'.str_replace('\\', '/', $class).'.php',
'$DIR_LIB/'.str_replace('\\', '/', $class).'.php',
);
$name = 'class '.$class;
foreach($fnames as $fn){
$_p = $this->get_file($this->document, $fn, $name);
if(false !== $_p){
$this->_run_php_1($_p);
return $_p;
}
}
return false;
}
protected function _run_php_1($part){
$code = $part->getBody();
$code = trim($code);
$code = trim($code, '<?>php ');
eval($code);
}
public function __construct($file = null, $offset = 0){
$this->buf = &$this;
if(null===$file)$file=__FILE__;
$this->__FILE__ = $file;
if(__FILE__===$this->__FILE__){
$this->offset = $this->getAttachmentOffset();
}else{
$this->offset = $offset;
}
$this->initial_offset = $this->offset;
//$this->php = array(
// '<?' => array(
//
// ),
// '#!' => array(
//
// ),
// '#' => array(
//
// ),
//);
// MimeStubApp::God()->addStreamWrapper( 'frdl', 'mime', $this, true ) ;
}
final public function __destruct(){
try{
if(is_resource($this->IO))fclose($this->IO);
}catch(\Exception $e){
trigger_error($e->getMessage(). ' in '.__METHOD__, $this->e_level);
}
}
public function __set($name, $value)
{
if('location'===$name){
$code =$this->__toString();
file_put_contents($value, $code);
return null;
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __set(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function getAttachmentOffset(){
return __COMPILER_HALT_OFFSET__;
}
public function __toString()
{
// $document = $this->document;
$code = $this->exports;
if(__FILE__ === $this->__FILE__) {
$php = substr($code, 0, $this->getAttachmentOffset());
}else{
$php = substr($code, 0, $this->initial_offset);
}
// $php = str_replace('define(\'___BLOCK_WEBFAN_MIME_VM_RUNNING_STUB___\', true);', 'define(\'___BLOCK_WEBFAN_MIME_VM_RUNNING_STUB___\', false);', $php);
$php = str_replace('define(\'___BLOCK_WEBFAN_MIME_VM_RUNNING_STUB___\', true);', '', $php);
$newClassName = "webfan\InstalShield\MimeStubAPC".mt_rand(10000000000000000,999999999999999999999999);
$php = preg_replace("/((\r\n|\r|\n)?namespace\swebfan;(\r\n|\r|\n)use\sfrdl;(\r\n|\r|\n))/", "\r\nnamespace ".$newClassName.";\r\n"."use frdl;".";\r\n", $php);
$mime = $this->document;
return $php.$mime;
}
public function __get($name)
{
switch($name){
case 'exports':
return $this->getFileAttachment($this->__FILE__, 0);
break;
case 'location':
return $this->__FILE__;
break;
case 'document':
if(false===$this->raw){
$this->raw=$this->getFileAttachment($this->__FILE__, $this->initial_offset);
}
if(false===$this->MIME){
$this->MIME=MimeStub::create($this->raw);
}
return $this->MIME;
break;
default:
return null;
break;
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function __invoke()
{
$args = func_get_args();
if(false===$this->raw){
$this->raw=$this->getFileAttachment($this->__FILE__, $this->initial_offset);
}
if(false===$this->MIME){
$this->MIME=MimeStub::create($this->raw);
}
$this->Request = new Request();
$this->Env = new Env();
$this->Context = new Context();
$this->Response = new Response();
$res = &$this;
if(0<count($args)){
$i=-1;
foreach($args as $arg){
$i++;
if(is_object($arg) && get_class($this->Request)===get_class($arg)){
$this->Request = &$arg;
}elseif(is_object($arg) && get_class($this->Env)===get_class($arg)){
$this->Env = &$arg;
}elseif(is_object($arg) && get_class($this->Context)===get_class($arg)){
$this->Context = &$arg;
}elseif(is_object($arg) && get_class($this->Response)===get_class($arg)){
$this->Response = &$arg;
}
if(is_array($arg)){
$this->Context = new Context($arg);
}if(is_string($arg)){
$cmd = $arg;
if('run'===$arg){
$res = call_user_func_array(array($this, '_run'), $args);
}else{
$u = parse_url($cmd);
$c = explode('.',$u['host']);
$c = array_reverse($c);
$tld = array_shift($c);
$f = false;
if('frdl'===$u['scheme']){
if('mime'===$tld){
if(!isset($args[$i+1])){
$res = $this->getFileAttachment($cmd, 0);
$f = true;
}else if(isset($args[$i+1])){
//@todo write
}
}
}
if(false===$f){
//todo...
//if('#'===substr($cmd, 0, 1)){
// $this->php['#'][]=$cmd;
//}elseif('#!'===substr($cmd, 0, 2)){
// $this->php['#!'][]=$cmd;
//}elseif('<?'===substr($cmd, 0, 2)){
// $this->php['<?'][]=$cmd;
//}else{
$parent = (isset($this->MIME->parent) && null !== $this->MIME->parent) ? $this->MIME->parent : null;
$this->MIME=MimeStub::create($cmd, $parent);
// }
}
}
}
}
}elseif(0===count($args)){
$res = &$this->buf;
}
return $res;
}
protected function _run(){
$this->runStubs();
return $this;
}
public function __call($name, $arguments)
{
return call_user_func_array(array($this->document, $name), $arguments);
}
public function getFileAttachment($file = null, $offset = null){
if(null === $file)$file = &$this->file;
if(null === $offset)$offset = $this->offset;
$IO = fopen($file, 'r');
fseek($IO, $offset);
try{
$buf = stream_get_contents($IO);
if(is_resource($IO))fclose($IO);
}catch(\Exception $e){
$buf = '';
if(is_resource($IO))fclose($IO);
trigger_error($e->getMessage(), $this->e_level);
}
return $buf;
}
}
class Request
{
function __construct(){
$this->SAPI = PHP_SAPI;
$this->argv = ('cli' ===$this->SAPI && isset($_SERVER['argv']) /* && isset($_SERVER['argv'][0])*/) ? $_SERVER['argv'][0] : false;
$this->protocoll = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? 'https' : 'http';
$this->method = $_SERVER['REQUEST_METHOD'];
$this->server_name = $_SERVER['SERVER_NAME'];
$this->origin = $_SERVER['HTTP_ORIGIN'];
$this->get = $_GET;
$this->post = $_POST;
$this->cookies = $_COOKIE;
$this->session = $_SESSION;
$this->uri = $_SERVER['REQUEST_URI'];
$this->parsed = parse_url($this->protocoll.'://'.$this->server_name.$this->uri);
switch($this->method){
case 'HEAD' :
case 'GET' :
$this->request = $_GET;
break;
case 'POST' :
case 'PUT' :
case 'DELETE' :
$this->request = $_POST;
break;
default :
$this->request = $_REQUEST;
break;
}
}
}
/**
* https://github.com/Riverline/multipart-parser
*
* Class Part
* @package Riverline\MultiPartParser
*
* Copyright (c) 2015-2016 Romain Cambien
*
* 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.
*
* - edited by webfan.de
*/
class MimeStub
{
const NS = __NAMESPACE__;
const DS = DIRECTORY_SEPARATOR;
const FILE = __FILE__;
const DIR = __DIR__;
const numbers = '0123456789';
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const specials = '!$%^&*()_+|~-=`{}[]:;<>?,./';
protected static $__i = -1;
//protected $_parent;
protected $_id = null;
protected $_p = -1;
/**
* @var array
*/
protected $headers;
/**
* @var string
*/
protected $body;
protected $_parent = null;
/**
* @var Part[]
*/
protected $parts = array();
/**
* @var bool
*/
protected $multipart = false;
protected $modified = false;
protected $contentType = false;
protected $encoding = false;
protected $charset = false;
protected $boundary = false;
protected function _defaultsRandchars ($opts = array()) {
$opts = array_merge(array(
'length' => 8,
'numeric' => true,
'letters' => true,
'special' => false
), $opts);
return array(
'length' => (is_int($opts['length'])) ? $opts['length'] : 8,
'numeric' => (is_bool($opts['numeric'])) ? $opts['numeric'] : true,
'letters' => (is_bool($opts['letters'])) ? $opts['letters'] : true,
'special' => (is_bool($opts['special'])) ? $opts['special'] : false
);
}
protected function _buildRandomChars ($opts = array()) {
$chars = '';
if ($opts['numeric']) { $chars .= self::numbers; }
if ($opts['letters']) { $chars .= self::letters; }
if ($opts['special']) { $chars .= self::specials; }
return $chars;
}
public function generateBundary($opts = array()) {
$opts = $this->_defaultsRandchars($opts);
$i = 0;
$rn = '';
$rnd = '';
$len = $opts['length'];
$randomChars = $this->_buildRandomChars($opts);
for ($i = 1; $i <= $len; $i++) {
$rn = mt_rand(0, strlen($randomChars) -1);
$n = substr($randomChars, $rn, 1);
$rnd .= $n;
}
return $rnd;
}
public function __set($name, $value)
{
$trace = debug_backtrace();
trigger_error(
'Undefined property via __set(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function __get($name)
{
// echo "Getting '$name'\n";
// if (array_key_exists($name, $this->data)) {
// return $this->data[$name];
// }
switch($name){
case 'parent':
return $this->_parent;
break;
case 'id':
return $this->_id;
break;
case 'nextChild':
$this->_p=++$this->_p;
if($this->_p >= count($this->parts)/* -1*/)return false;
return (is_array($this->parts)) ? $this->parts[$this->_p] : null;
break;
case 'next':
return $this->nextChild;
break;
case 'rewind':
$this->_p=-1;
return $this;
case 'root':
if(null === $this->parent || (get_class($this->parent) !== get_class($this)))return $this;
return $this->parent->root;
break;
case 'isRoot':
return ($this->root->id === $this->id) ? true : false;
break;
case 'lastChild':
return (is_array($this->parts)) ? $this->parts[count($this->parts)-1] : null;
break;
case 'firstChild':
return (is_array($this->parts) && isset($this->parts[0])) ? $this->parts[0] : null;
break;
default:
return null;
break;
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function __call($name, $arguments)
{
if('append'===$name){
if(!isset($arguments[0]))$arguments[0]='';
$this->parts[] = new self($arguments[0], $this);
return $this;
}
//https://tools.ietf.org/id/draft-snell-http-batch-00.html
foreach(array('from', 'to', 'sender', 'subject', 'reply-to'/* ->{'reply-to'} */, 'in-reply-to',
'message-id') as $_header){
if($_header===$name){
if(0===count($arguments)){
return $this->getHeader($_header, null);
}elseif(null===$arguments[0]){
$this->removeHeader($_header);
}elseif(isset($arguments[0]) && is_string($arguments[0])){
$this->setHeader($_header, $arguments[0]);
}
return $this;
}
}
// Note: value of $name is case sensitive.
$trace = debug_backtrace();
trigger_error(
'Undefined property via __call(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/** As of PHP 5.3.0 */
public static function __callStatic($name, $arguments)
{
if('run'===$name){
return call_user_func_array('run', $arguments);
}
if('vm'===$name){
if(0===count($arguments)){
return new MimeVM();
}elseif(1===count($arguments)){
return new MimeVM($arguments[0]);
}elseif(2===count($arguments)){
return new MimeVM($arguments[0], $arguments[1]);
}
// return call_user_func_array(array(webfan\MimeVM, '__construct'), $arguments);
return new MimeVM();
}
if('create'===$name){
if(!isset($arguments[0]))$arguments[0]='';
if(!isset($arguments[1]))$arguments[1]=null;
return new self($arguments[0], $arguments[1]);
}
// Note: value of $name is case sensitive.
$trace = debug_backtrace();
trigger_error(
'Undefined property via __callStatic(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function getContentType()
{
$this->contentType=$this->getMimeType();
return $this->contentType;
}
public function headerName($headName)
{
$headName = str_replace('-', ' ', $headName);
$headName = ucwords($headName);
return preg_replace("/\s+/", "\s", str_replace(' ', '-', $headName));
}
/**
* @param string $input A base64 encoded string
*
* @return string A decoded string
*/
public static function urlsafeB64Decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* @param string $input Anything really
*
* @return string The base64 encode of what you passed in
*/
public static function urlsafeB64Encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
public static function strip_body($s,$s1,$s2=false,$offset=0, $_trim = true) {
/*
* http://php.net/manual/en/function.strpos.php#75146
*/
// if( $s2 === false ) { $s2 = $s1; }
if( $s2 === false ) { $s2 = $s1.'--'; }
$result = array();
$result_2 = array();
$L1 = strlen($s1);
$L2 = strlen($s2);
if( $L1==0 || $L2==0 ) {
return false;
}
do {
$pos1 = strpos($s,$s1,$offset);
if( $pos1 !== false ) {
$pos1 += $L1;
$pos2 = strpos($s,$s2,$pos1);
if( $pos2 !== false ) {
$key_len = $pos2 - $pos1;
$this_key = substr($s,$pos1,$key_len);
if(true===$_trim){
$this_key = trim($this_key);
}
if( !array_key_exists($this_key,$result) ) {
$result[$this_key] = array();
}
$result[$this_key][] = $pos1;
$result_2[] = array(
'pos' => $pos1,
'content' => $this_key
);
$offset = $pos2 + $L2;
} else {
$pos1 = false;
}
}
} while($pos1 !== false );
return array(
'pindex' => $result_2,
'cindex' => $result
);
}
/**
* MultiPart constructor.
* @param string $content
* @throws \InvalidArgumentException
*/
protected function __construct($content, &$parent = null)
{
$this->_id = ++self::$__i;
$this->_parent = $parent;
// Split headers and body
$splits = preg_split('/(\r?\n){2}/', $content, 2);
if (count($splits) < 2) {
throw new \InvalidArgumentException("Content is not valid, can't split headers and content");
}
list ($headers, $body) = $splits;
// Regroup multiline headers
$currentHeader = '';
$headerLines = array();
foreach (preg_split('/\r?\n/', $headers) as $line) {
if (empty($line)) {
continue;
}
if (preg_match('/^\h+(.+)/', $line, $matches)) {
// Multi line header
$currentHeader .= ' '.$matches[1];
} else {
if (!empty($currentHeader)) {
$headerLines[] = $currentHeader;
}
$currentHeader = trim($line);
}
}
if (!empty($currentHeader)) {
$headerLines[] = $currentHeader;
}
// Parse headers
$this->headers = array();
foreach ($headerLines as $line) {
$lineSplit = explode(':', $line, 2);
if (2 === count($lineSplit)) {
list($key, $value) = $lineSplit;
// Decode value
$value = mb_decode_mimeheader(trim($value));
} else {
// Bogus header
$key = $lineSplit[0];
$value = '';
}
// Case-insensitive key
$key = strtolower($key);
if (!isset($this->headers[$key])) {
$this->headers[$key] = $value;
} else {
if (!is_array($this->headers[$key])) {
$this->headers[$key] = (array)$this->headers[$key];
}
$this->headers[$key][] = $value;
}
}
// Is MultiPart ?
$contentType = $this->getHeader('Content-Type');
$this->contentType=$contentType;
if ('multipart' === strstr(self::getHeaderValue($contentType), '/', true)) {
// MultiPart !
$this->multipart = true;
$boundary = self::getHeaderOption($contentType, 'boundary');
$this->boundary=$boundary;
if (null === $boundary) {
throw new \InvalidArgumentException("Can't find boundary in content type");
}
$separator = '--'.preg_quote($boundary, '/');
if (0 === preg_match('/'.$separator.'\r?\n(.+?)\r?\n'.$separator.'--/s', $body, $matches)
|| preg_last_error() !== PREG_NO_ERROR
) {
$bodyParts = self::strip_body($body,$separator."",$separator."--",0);
if(1 !== count($bodyParts['pindex'])){
throw new \InvalidArgumentException("Can't find multi-part content");
}
$bodyStr = $bodyParts['pindex'][0]['content'];
}else{
$bodyStr = $matches[1];
}
$parts = preg_split('/\r?\n'.$separator.'\r?\n/', $bodyStr);
unset($bodyStr);
foreach ($parts as $part) {
//$this->parts[] = new self($part, $this);
$this->append($part);
}
} else {
// Decode
$encoding = $this->getEcoding();
switch ($encoding) {
case 'base64':
$body = $this->urlsafeB64Decode($body);
break;
case 'quoted-printable':
$body = quoted_printable_decode($body);
break;
}
// Convert to UTF-8 ( Not if binary or 7bit ( aka Ascii ) )
if (!in_array($encoding, array('binary', '7bit'))) {
// Charset
$charset = self::getHeaderOption($contentType, 'charset');
if (null === $charset) {
// Try to detect
$charset = mb_detect_encoding($body) ?: 'utf-8';
}
$this->charset=$charset;
// Only convert if not UTF-8
if ('utf-8' !== strtolower($charset)) {
$body = mb_convert_encoding($body, 'utf-8', $charset);
}
}
$this->body = $body;
}
}
public function __toString()
{
$boundary = $this->getBoundary($this->isMultiPart());
$s='';
foreach($this->headers as $hname => $hvalue){
$s.= $this->headerName($hname).': '. $this->getHeader($hname) /*$hvalue*/."\r\n";
}
$s.= "\r\n" ;
if ($this->isMultiPart()) $s.= "--" ;
$s.= $boundary ;
if ($this->isMultiPart()) $s.= "\r\n" ;
if ($this->isMultiPart()) {
foreach ($this->parts as $part) {
$s.= (get_class($this) === get_class($part)) ? $part : $part->__toString() . "\r\n" ;
}
$s.= "\r\n"."--" . $boundary . '--';
}else{
$s.= $this->getBody(true, $encoding);
}
if (null!==$this->parent && $this->parent->isMultiPart() && $this->parent->lastChild->id !== $this->id){
$s.= "\r\n" . "--" .$this->parent->getBoundary() . "\r\n";
}
return $s;
}
public function getEcoding()
{
$this->encoding=strtolower($this->getHeader('Content-Transfer-Encoding'));
return $this->encoding;
}
public function getCharset()
{
// return $this->charset;
$charset = self::getHeaderOption($this->getMimeType(), 'charset');
if(!is_string($charset)) {
// Try to detect
$charset = mb_detect_encoding($this->body) ?: 'utf-8';
}
$this->charset=$charset;
return $this->charset;
}
public function setBoundary($boundary = null, $opts = array())
{
$this->mod();
if(null===$boundary){
$size = 8;
if(8 < count($this->parts))$size = 16;
if(16 < count($this->parts))$size = 24;
if(75 < count($this->parts))$size = 32;
if(200 < count($this->parts))$size = 64;
$opt = array(
'length' => $size
);
$options = array_merge($opt, $opts);
$boundary = $this->generateBundary($options);
}
$this->boundary =$boundary;
$this->setHeaderOption('Content-Type', $this->boundary, 'boundary');
}
public function getBoundary($generate = true)
{
$this->boundary = self::getHeaderOption($this->getHeader('Content-Type'), 'boundary');
if(true === $generate && $this->isMultiPart()
&& (!is_string($this->boundary) || 0===strlen(trim($this->boundary)))
){
$this->setBoundary();
}
return $this->boundary;
}
/**
* @param string $key
* @param mixed $default
* @return mixed
*/
public function mod()
{
$this->modified = true;
return $this;
}
public function setHeader($key, $value)
{
$this->mod();
$key = strtolower($key);
$this->headers[$key]=$value;
// echo print_r($this->headers, true);
return $this;
}
public function removeHeader($key)
{
$this->mod();
unset($this->headers[$key]);
return $this;
}
public function setHeaderOption($headerName, $value = null, $opt = null)
{
$this->mod();
$old_header_value = $this->getHeader($headerName);
if(null===$opt && null !==$value){
$this->headers[$headerName]=$value;
}else if(null !==$opt && null !==$value){
list($headerValue,$options) = self::parseHeaderContent($old_header_value);
$options[$opt]=$value;
$new_header_value = $headerValue;
// $new_header_value='';
foreach($options as $o => $v){
$new_header_value .= ';'.$o.'='.$v.'';
}
$this->setHeader($headerName, $new_header_value);
}
return $this;
}
/**
* @return bool
*/
public function isMultiPart()
{
return $this->multipart;
}
/**
* @return string
* @throws \LogicException if is multipart
*/
public function getBody($reEncode = false, &$encoding = null)
{
if ($this->isMultiPart()) {
throw new \LogicException("MultiPart content, there aren't body");
} else {
$body = $this->body;
if(true===$reEncode){
$encoding = $this->getEcoding();
switch ($encoding) {
case 'base64':
$body = $this->urlsafeB64Encode($body);
break;
case 'quoted-printable':
$body = quoted_printable_encode($body);
break;
}
// Convert to UTF-8 ( Not if binary or 7bit ( aka Ascii ) )
if (!in_array($encoding, array('binary', '7bit'))) {
// back de-/encode
if ( 'utf-8' !== strtolower(self::getHeaderOption($this->getMimeType(), 'charset'))
&& 'utf-8' === mb_detect_encoding($body)) {
$body = mb_convert_encoding($body, self::getHeaderOption($this->getMimeType(), 'charset'), 'utf-8');
}elseif ( 'utf-8' === strtolower(self::getHeaderOption($this->getMimeType(), 'charset'))
&& 'utf-8' !== mb_detect_encoding($body)) {
$body = mb_convert_encoding($body, 'utf-8', mb_detect_encoding($body));
}
}
}
return $body;
}
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @param string $key
* @param mixed $default
* @return mixed
*/
public function getHeader($key, $default = null)
{
// Case-insensitive key
$key = strtolower($key);
if (isset($this->headers[$key])) {
return $this->headers[$key];
} else {
return $default;
}
}
/**
* @param string $content
* @return array
*/
static protected function parseHeaderContent($content)
{
$parts = explode(';', $content);
$headerValue = array_shift($parts);
$options = array();
// Parse options
foreach ($parts as $part) {
if (!empty($part)) {
$partSplit = explode('=', $part, 2);
if (2 === count($partSplit)) {
list ($key, $value) = $partSplit;
$options[trim($key)] = trim($value, ' "');
} else {
// Bogus option
$options[$partSplit[0]] = '';
}
}
}
return array($headerValue, $options);
}
/**
* @param string $header
* @return string
*/
static public function getHeaderValue($header)
{
list($value) = self::parseHeaderContent($header);
return $value;
}
/**
* @param string $header
* @return string
*/
static public function getHeaderOptions($header)
{
list(,$options) = self::parseHeaderContent($header);
return $options;
}
/**
* @param string $header
* @param string $key
* @param mixed $default
* @return mixed
*/
static public function getHeaderOption($header, $key, $default = null)
{
$options = self::getHeaderOptions($header);
if (isset($options[$key])) {
return $options[$key];
} else {
return $default;
}
}
/**
* @return string
*/
public function getMimeType()
{
// Find Content-Disposition
$contentType = $this->getHeader('Content-Type');
return self::getHeaderValue($contentType) ?: 'application/octet-stream';
}
/**
* @return string|null
*/
public function getName()
{
// Find Content-Disposition
$contentDisposition = $this->getHeader('Content-Disposition');
return self::getHeaderOption($contentDisposition, 'name');
}
/**
* @return string|null
*/
public function getFileName()
{
// Find Content-Disposition
$contentDisposition = $this->getHeader('Content-Disposition');
return self::getHeaderOption($contentDisposition, 'filename');
}
/**
* @return bool
*/
public function isFile()
{
return !is_null($this->getFileName());
}
/**
* @return Part[]
* @throws \LogicException if is not multipart
*/
public function getParts()
{
if ($this->isMultiPart()) {
return $this->parts;
} else {
throw new \LogicException("Not MultiPart content, there aren't any parts");
}
}
/**
* @param string $name
* @return Part[]
* @throws \LogicException if is not multipart
*/
public function getPartsByName($name)
{
$parts = array();
foreach ($this->getParts() as $part) {
if ($part->getName() === $name) {
$parts[] = $part;
}
}
return $parts;
}
}
__halt_compiler();Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=hoHoBundary12344dh
To: test@example.com
--hoHoBundary12344dh
Content-Type: multipart/alternate;boundary=EVGuDPPT
--EVGuDPPT
Content-Type: text/html;charset=utf-8
<h1>Test</h1>
<p>This is the <strong>HTML</strong> version of the message.</p>
--EVGuDPPT
Content-Type: text/plain;charset=utf-8
This is the plain text version of the message.
--EVGuDPPT
Content-Type: multipart/related;boundary=4444EVGuDPPT
Content-Disposition: php ;filename="$__FILE__/stub.zip";name="archive stub.zip"
--4444EVGuDPPT
Content-Type: application/x-httpd-php;charset=utf-8
Content-Disposition: php ;filename="$STUB/bootstrap.php";name="stub bootstrap.php"
<?php
spl_autoload_register(array($this,'Autoload'), true, true);
--4444EVGuDPPT
Content-Type: application/x-httpd-php;charset=utf-8
Content-Disposition: php ;filename="$HOME/index.php";name="stub index.php"
<?php
echo 'Hello World!';
echo '<br /><br />Test: ';
try{
$o = new \O_Test;
}catch(\Exception $e){
$o = new \stdclass;
}
if('O_Test'===get_class($o)){
echo 'OK';
}else{
echo ' ERROR';
}
?>
--4444EVGuDPPT--
--EVGuDPPT--
--hoHoBundary12344dh
Content-Type: multipart/related;boundary=3333EVGuDPPT
--3333EVGuDPPT
Content-Type: application/x-httpd-php;charset=utf-8
Content-Disposition: php ;filename="$DIR_PSR4/O_Test.php";name="class O_Test"
<?php
/**
* Compression Shortcut
*/
class O_Test extends \stdclass{}
--3333EVGuDPPT--
--hoHoBundary12344dh--